Commit feaa2746 authored by Jason Robinson's avatar Jason Robinson Committed by GitHub

Merge pull request #288 from jaywink/streams

Streams URL changes
parents 79e454ac 44d38c87
......@@ -33,7 +33,9 @@ urlpatterns = [
url(r"", include("socialhome.federate.urls", namespace="federate")),
# Streams
url(r"", include("socialhome.streams.urls", namespace="streams")),
url(r"^streams/", include("socialhome.streams.urls", namespace="streams")),
# Legacy streams urls support
url(r"", include("socialhome.streams.urls_legacy", namespace="streams-legacy")),
url(r"^$", HomeView.as_view(), name="home"),
......
......@@ -18,6 +18,12 @@ Changed
* Previously previews and oEmbed's for content used to only pick up "orphan" links from the content text. This meant that if there was a Markdown or HTML link, there would be no link preview or oEmbed fetched. This has now been changed. All links found in the content will be considered for preview and oEmbed. The first link to return a preview or oEmbed will be used.
* Streams URL changes:
* All streams will now be under `/streams/` for a cleaner URL layout. So for example `/public/` is now `/streams/public/`.
* Tag stream URL has been changed from `/streams/tags/<tag>/` to `/streams/tag/<tag>/`. This small change allows us to later map `/stream/tags/` to the tags the user is following.
Since lots of old content will point to the old URL's, there will be support for the legacy URL's until they are needed for something else in the future.
Fixed
.....
......
......@@ -344,7 +344,7 @@ class Content(models.Model):
"#%s" % candidate,
"[#%s](%s)" % (
candidate,
reverse("streams:tags", kwargs={"name": candidate.lower()})
reverse("streams:tag", kwargs={"name": candidate.lower()})
)
)
final_words.append(tag_word)
......
......@@ -35,16 +35,16 @@ class ContentQuerySet(models.QuerySet):
def public(self):
return self.top_level()._select_related().filter(visibility=Visibility.PUBLIC).order_by("-created")
def tags(self, tag, user):
def tag(self, tag, user):
return self.top_level()._select_related().visible_for_user(user).filter(tags=tag).order_by("-created")
def tags_by_name(self, tag, user):
def tag_by_name(self, tag, user):
from socialhome.content.models import Tag, Content
try:
tag = Tag.objects.get_by_cleaned_name(tag)
except Tag.DoesNotExist:
return Content.objects.none()
return self.tags(tag, user)
return self.tag(tag, user)
def followed(self, user):
qs = self.top_level()._select_related().visible_for_user(user)
......
......@@ -73,7 +73,7 @@ def notify_listeners(content):
StreamConsumer.group_send("streams_public", data)
# Tag streams
for tag in content.tags.all():
StreamConsumer.group_send("streams_tags__%s" % tag.channel_group_name, data)
StreamConsumer.group_send("streams_tag__%s" % tag.channel_group_name, data)
# Profile streams
StreamConsumer.group_send("streams_profile__%s" % content.author.id, data)
StreamConsumer.group_send("streams_profile_all__%s" % content.author.id, data)
......
......@@ -410,7 +410,8 @@ class TestContentRendered(SocialhomeTestCase):
content = Content.objects.create(
text="<img src='localhost'> #nsfw", guid="barfoo", author=ProfileFactory()
)
self.assertEqual(content.rendered, '<p><img class="nsfw" src="localhost"/> <a href="/tags/nsfw/">#nsfw</a></p>')
self.assertEqual(content.rendered, '<p><img class="nsfw" src="localhost"/> '
'<a href="/streams/tag/nsfw/">#nsfw</a></p>')
def test_renders_with_oembed(self):
content = Content.objects.create(
......@@ -429,7 +430,8 @@ class TestContentRendered(SocialhomeTestCase):
def test_renders_linkified_tags(self):
content = ContentFactory(text="#tag #MiXeD")
self.assertEqual(content.rendered, '<p><a href="/tags/tag/">#tag</a> <a href="/tags/mixed/">#MiXeD</a></p>')
self.assertEqual(content.rendered, '<p><a href="/streams/tag/tag/">#tag</a> '
'<a href="/streams/tag/mixed/">#MiXeD</a></p>')
class TestContentSaveTags(SocialhomeTestCase):
......
......@@ -13,14 +13,14 @@ class TestContentQuerySet(SocialhomeTestCase):
def setUpTestData(cls):
super().setUpTestData()
cls.public_content = ContentFactory(pinned=True)
cls.public_tags_content = ContentFactory(text="#foobar")
cls.public_tag_content = ContentFactory(text="#foobar")
cls.limited_content = ContentFactory(visibility=Visibility.LIMITED)
cls.tag = Tag.objects.get(name="foobar")
cls.site_content = ContentFactory(visibility=Visibility.SITE, pinned=True)
cls.site_tags_content = ContentFactory(visibility=Visibility.SITE, text="#foobar")
cls.site_tag_content = ContentFactory(visibility=Visibility.SITE, text="#foobar")
cls.self_user = UserFactory()
cls.self_content = ContentFactory(visibility=Visibility.SELF, author=cls.self_user.profile, pinned=True)
cls.self_tags_content = ContentFactory(visibility=Visibility.SELF, author=cls.self_user.profile, text="#foobar")
cls.self_tag_content = ContentFactory(visibility=Visibility.SELF, author=cls.self_user.profile, text="#foobar")
cls.other_user = UserFactory()
cls.anonymous_user = AnonymousUser()
cls.other_user.profile.following.add(cls.public_content.author, cls.self_user.profile)
......@@ -39,37 +39,37 @@ class TestContentQuerySet(SocialhomeTestCase):
def test_visible_for_user(self):
contents = set(Content.objects.visible_for_user(self.anonymous_user))
self.assertEqual(contents, {self.public_content, self.public_tags_content, self.public_reply,
self.assertEqual(contents, {self.public_content, self.public_tag_content, self.public_reply,
self.public_share, self.public_share_reply})
contents = set(Content.objects.visible_for_user(self.other_user))
self.assertEqual(contents, {self.public_content, self.public_tags_content, self.site_content,
self.site_tags_content, self.public_reply, self.site_reply, self.public_share,
self.assertEqual(contents, {self.public_content, self.public_tag_content, self.site_content,
self.site_tag_content, self.public_reply, self.site_reply, self.public_share,
self.site_share, self.public_share_reply, self.share_site_reply})
contents = set(Content.objects.visible_for_user(self.self_content.author.user))
self.assertEqual(contents, {self.public_content, self.public_tags_content, self.site_content,
self.site_tags_content, self.self_content, self.self_tags_content,
self.assertEqual(contents, {self.public_content, self.public_tag_content, self.site_content,
self.site_tag_content, self.self_content, self.self_tag_content,
self.public_reply, self.site_reply, self.public_share,
self.site_share, self.public_share_reply, self.share_site_reply})
def test_public(self):
contents = set(Content.objects.public())
self.assertEqual(contents, {self.public_content, self.public_tags_content})
def test_tags_by_name(self):
contents = set(Content.objects.tags_by_name("foobar", self.anonymous_user))
self.assertEqual(contents, {self.public_tags_content})
contents = set(Content.objects.tags_by_name("foobar", self.other_user))
self.assertEqual(contents, {self.public_tags_content, self.site_tags_content})
contents = set(Content.objects.tags_by_name("foobar", self.self_content.author.user))
self.assertEqual(contents, {self.public_tags_content, self.site_tags_content, self.self_tags_content})
def test_tags(self):
contents = set(Content.objects.tags(self.tag, self.anonymous_user))
self.assertEqual(contents, {self.public_tags_content})
contents = set(Content.objects.tags(self.tag, self.other_user))
self.assertEqual(contents, {self.public_tags_content, self.site_tags_content})
contents = set(Content.objects.tags(self.tag, self.self_content.author.user))
self.assertEqual(contents, {self.public_tags_content, self.site_tags_content, self.self_tags_content})
self.assertEqual(contents, {self.public_content, self.public_tag_content})
def test_tag_by_name(self):
contents = set(Content.objects.tag_by_name("foobar", self.anonymous_user))
self.assertEqual(contents, {self.public_tag_content})
contents = set(Content.objects.tag_by_name("foobar", self.other_user))
self.assertEqual(contents, {self.public_tag_content, self.site_tag_content})
contents = set(Content.objects.tag_by_name("foobar", self.self_content.author.user))
self.assertEqual(contents, {self.public_tag_content, self.site_tag_content, self.self_tag_content})
def test_tag(self):
contents = set(Content.objects.tag(self.tag, self.anonymous_user))
self.assertEqual(contents, {self.public_tag_content})
contents = set(Content.objects.tag(self.tag, self.other_user))
self.assertEqual(contents, {self.public_tag_content, self.site_tag_content})
contents = set(Content.objects.tag(self.tag, self.self_content.author.user))
self.assertEqual(contents, {self.public_tag_content, self.site_tag_content, self.self_tag_content})
def test_followed(self):
contents = set(Content.objects.followed(self.other_user))
......@@ -100,7 +100,7 @@ class TestContentQuerySet(SocialhomeTestCase):
contents = set(Content.objects.profile(self.site_content.author.guid, self.self_user))
self.assertEqual(contents, set())
contents = set(Content.objects.profile(self.self_content.author.guid, self.self_user))
self.assertEqual(contents, {self.self_content, self.self_tags_content})
self.assertEqual(contents, {self.self_content, self.self_tag_content})
self._set_profiles_public()
contents = set(Content.objects.profile(self.public_content.author.guid, self.anonymous_user))
......@@ -120,7 +120,7 @@ class TestContentQuerySet(SocialhomeTestCase):
contents = set(Content.objects.profile(self.site_content.author.guid, self.self_user))
self.assertEqual(contents, {self.site_content})
contents = set(Content.objects.profile(self.self_content.author.guid, self.self_user))
self.assertEqual(contents, {self.self_content, self.self_tags_content})
self.assertEqual(contents, {self.self_content, self.self_tag_content})
def test_profile_by_id(self):
contents = set(Content.objects.profile_by_id(self.public_content.author.id, self.anonymous_user))
......@@ -140,7 +140,7 @@ class TestContentQuerySet(SocialhomeTestCase):
contents = set(Content.objects.profile_by_id(self.site_content.author.id, self.self_user))
self.assertEqual(contents, set())
contents = set(Content.objects.profile_by_id(self.self_content.author.id, self.self_user))
self.assertEqual(contents, {self.self_content, self.self_tags_content})
self.assertEqual(contents, {self.self_content, self.self_tag_content})
self._set_profiles_public()
contents = set(Content.objects.profile_by_id(self.public_content.author.id, self.anonymous_user))
......@@ -160,7 +160,7 @@ class TestContentQuerySet(SocialhomeTestCase):
contents = set(Content.objects.profile_by_id(self.site_content.author.id, self.self_user))
self.assertEqual(contents, {self.site_content})
contents = set(Content.objects.profile_by_id(self.self_content.author.id, self.self_user))
self.assertEqual(contents, {self.self_content, self.self_tags_content})
self.assertEqual(contents, {self.self_content, self.self_tag_content})
def test_profile_pinned(self):
contents = set(Content.objects.profile_pinned(self.public_content.author.guid, self.anonymous_user))
......
......@@ -31,8 +31,8 @@ class TestNotifyListeners(SocialhomeTestCase):
content = ContentFactory(visibility=Visibility.LIMITED, text="#foobar #barfoo")
data = json.dumps({"event": "new", "id": content.id})
calls = [
call("streams_tags__%s_foobar" % Tag.objects.get(name="foobar").id, data),
call("streams_tags__%s_barfoo" % Tag.objects.get(name="barfoo").id, data),
call("streams_tag__%s_foobar" % Tag.objects.get(name="foobar").id, data),
call("streams_tag__%s_barfoo" % Tag.objects.get(name="barfoo").id, data),
call("streams_profile__%s" % content.author.id, data),
call("streams_profile_all__%s" % content.author.id, data),
]
......
......@@ -156,6 +156,6 @@ class TestProcessTextLinks(TestCase):
def test_does_not_add_target_blank_if_link_is_internal(self):
self.assertEqual(
process_text_links('<a href="/tags/foobar">#foobar</a>'),
'<a href="/tags/foobar">#foobar</a>',
process_text_links('<a href="/streams/tag/foobar">#foobar</a>'),
'<a href="/streams/tag/foobar">#foobar</a>',
)
......@@ -31,10 +31,10 @@ class StreamConsumer(WebsocketConsumer):
return Content.objects.public()
elif stream_info[0] == "followed":
return Content.objects.followed(self.message.user)
elif stream_info[0] == "tags":
elif stream_info[0] == "tag":
tag_id = stream_info[1].split("_")[0]
tag = Tag.objects.get(id=tag_id)
return Content.objects.tags(tag, self.message.user)
return Content.objects.tag(tag, self.message.user)
elif stream_info[0] == "profile":
return Content.objects.profile_pinned(stream_info[1], self.message.user)
elif stream_info[0] == "profile_all":
......
......@@ -73,11 +73,11 @@ class TestStreamConsumerReceive(ChannelTestCase):
self.assertEqual(text["contents"], [self.child_content.dict_for_view(AnonymousUser())])
@patch("socialhome.streams.consumers.Content.objects.public", return_value=Content.objects.none())
@patch("socialhome.streams.consumers.Content.objects.tags", return_value=Content.objects.none())
@patch("socialhome.streams.consumers.Content.objects.tag", return_value=Content.objects.none())
@patch("socialhome.streams.consumers.Content.objects.profile_by_id", return_value=Content.objects.none())
@patch("socialhome.streams.consumers.Content.objects.profile_pinned", return_value=Content.objects.none())
@patch("socialhome.streams.consumers.Content.objects.followed", return_value=Content.objects.none())
def test_get_stream_qs_per_stream(self, mock_followed, mock_pinned, mock_profile, mock_tags, mock_public):
def test_get_stream_qs_per_stream(self, mock_followed, mock_pinned, mock_profile, mock_tag, mock_public):
self.client.send_and_consume(
"websocket.receive",
{
......@@ -89,12 +89,12 @@ class TestStreamConsumerReceive(ChannelTestCase):
self.client.send_and_consume(
"websocket.receive",
{
"path": "/ch/streams/tags__%s_%s/" % (self.tag.id, self.tag.name),
"path": "/ch/streams/tag__%s_%s/" % (self.tag.id, self.tag.name),
"text": '{"action": "load_more", "last_id": %s}' % self.content2.id,
},
)
self.assertEqual(mock_tags.call_count, 1)
self.assertEqual(mock_tags.call_args[0][0], self.tag)
self.assertEqual(mock_tag.call_count, 1)
self.assertEqual(mock_tag.call_args[0][0], self.tag)
self.client.send_and_consume(
"websocket.receive",
{
......
......@@ -45,7 +45,7 @@ class TestPublicStreamView(TestCase):
assert response.status_code == 200
class TestTagsStreamView(TestCase):
class TestTagStreamView(TestCase):
@classmethod
def setUpTestData(cls):
super().setUpTestData()
......@@ -54,22 +54,22 @@ class TestTagsStreamView(TestCase):
cls.client = Client()
def test_context_data_is_ok(self):
response = self.client.get(reverse("streams:tags", kwargs={"name": "tagnocontent"}))
response = self.client.get(reverse("streams:tag", kwargs={"name": "tagnocontent"}))
assert response.context["tag_name"] == "tagnocontent"
def test_renders_without_content(self):
response = self.client.get(reverse("streams:tags", kwargs={"name": "tagnocontent"}))
response = self.client.get(reverse("streams:tag", kwargs={"name": "tagnocontent"}))
assert "#%s" % self.tag_no_content.name in str(response.content)
assert not response.context["content_list"]
assert response.status_code == 200
def test_renders_with_content(self):
response = self.client.get(reverse("streams:tags", kwargs={"name": "tag"}))
response = self.client.get(reverse("streams:tag", kwargs={"name": "tag"}))
assert response.status_code == 200
assert self.content.rendered in str(response.content)
def test_uses_correct_template(self):
response = self.client.get(reverse("streams:tags", kwargs={"name": "tagnocontent"}))
response = self.client.get(reverse("streams:tag", kwargs={"name": "tagnocontent"}))
template_names = [template.name for template in response.templates]
assert "streams/tag.html" in template_names
......@@ -78,7 +78,7 @@ class TestTagsStreamView(TestCase):
site = ContentFactory(text="#tag site", visibility=Visibility.SITE)
selff = ContentFactory(text="#tag self", visibility=Visibility.SELF)
limited = ContentFactory(text="#tag limited", visibility=Visibility.LIMITED)
response = self.client.get(reverse("streams:tags", kwargs={"name": "tag"}))
response = self.client.get(reverse("streams:tag", kwargs={"name": "tag"}))
assert content.rendered in str(response.content)
assert site.rendered not in str(response.content)
assert selff.rendered not in str(response.content)
......
......@@ -5,5 +5,5 @@ from socialhome.streams.views import PublicStreamView, TagStreamView, FollowedSt
urlpatterns = [
url(r"^followed/$", FollowedStreamView.as_view(), name="followed"),
url(r"^public/$", PublicStreamView.as_view(), name="public"),
url(r"^tags/(?P<name>[\w-]+)/$", TagStreamView.as_view(), name="tags"),
url(r"^tag/(?P<name>[\w-]+)/$", TagStreamView.as_view(), name="tag"),
]
from django.conf.urls import url
from socialhome.streams.views import PublicStreamView, TagStreamView, FollowedStreamView
# This file contains legacy versions of the stream URL's that were in effect before the changes introduced
# on 4th September 2017. Remove these if they are required for something else in the future, but notice there
# will be some content linking to these pre this change.
urlpatterns = [
url(r"^followed/$", FollowedStreamView.as_view(), name="followed"),
url(r"^public/$", PublicStreamView.as_view(), name="public"),
url(r"^tags/(?P<name>[\w-]+)/$", TagStreamView.as_view(), name="tag"),
]
from braces.views import LoginRequiredMixin
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext
from django.views.generic import ListView
from django.urls import reverse
from socialhome.content.models import Content, Tag
......@@ -78,12 +76,12 @@ class TagStreamView(BaseStreamView):
def dispatch(self, request, *args, **kwargs):
self.tag = get_object_or_404(Tag, name=kwargs.get("name"))
self.stream_name = "tags__%s" % self.tag.channel_group_name
self.stream_name = "tag__%s" % self.tag.channel_group_name
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
"""Restrict to a tag."""
return Content.objects.tags(self.tag, self.request.user)
return Content.objects.tag(self.tag, self.request.user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment