Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ Depends on
* auth, admin, comments
* django-model-utils
* python-pytz
* markdown
* textile
* oembed
* pyembed-markdown
* six

Huboard
-------
Expand Down
10 changes: 8 additions & 2 deletions django_mesh/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from django.contrib import admin

# App imports
from .models import Channel, Post, Tag
from .models import Channel, Post, Tag, Media

class ChannelAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
Expand All @@ -42,4 +42,10 @@ def formfield_for_foreignkey(self, db_field, request, **kwargs):
db_field.default = request.user
return super(PostAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

admin.site.register(Post, PostAdmin)
admin.site.register(Post, PostAdmin)

class MediaAdmin(admin.ModelAdmin):
list_display = ('title', 'media_type')

admin.site.register(Media, MediaAdmin)

17 changes: 14 additions & 3 deletions django_mesh/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
#along with this program. If not, see <http://www.gnu.org/licenses/>.

# Django imports

from django.db import models
from django.utils import timezone
from django.db.models.query import QuerySet
from django.db.models import Q
from django.db.models.query import QuerySet
from django.utils import timezone


class PostQuerySet(QuerySet):
Expand Down Expand Up @@ -48,4 +49,14 @@ def get_for_user(self, user):
if user.id is not None:
q_object = Q(post__channel__followers=user.id) | q_object

return self.filter(q_object).distinct().filter(post__published__lte=timezone.now())
return self.filter(q_object).distinct().filter(post__published__lte=timezone.now())

class MediaQuerySet(QuerySet):
def get_for_user(self, user):

q_object = Q(channel__public=True)

if user.id is not None:
q_object = Q(channel__followers=user.id) | q_object

return self.filter(q_object).distinct()
131 changes: 117 additions & 14 deletions django_mesh/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,48 +17,96 @@
# Python imports
from __future__ import unicode_literals
import re

import six
# Django imports
#from django.contrib.sitemaps import ping_google
from django.utils.encoding import python_2_unicode_compatible

from django.core.urlresolvers import reverse
from django.db import models
from django.utils import timezone
from django.conf import settings


# 3d party imports
from model_utils import Choices
import markdown
import textile # to do: add oembed for textile markup
from pyembed.markdown import PyEmbedMarkdown
from pyembed.core import PyEmbed
from bs4 import BeautifulSoup

# App imports
from .managers import PostQuerySet, ChannelQuerySet, TagQuerySet
from .managers import PostQuerySet, ChannelQuerySet, TagQuerySet, MediaQuerySet

# URL_REGEX = r"""(?i)\b((?:https?:(?:/{1,3}|[a-z0-9%])|[a-z0-9.\-]+[.](?:com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)/)(?:[^\s()<>{}\[\]]+|\([^\s()]*?\([^\s()]+\)[^\s()]*?\)|\([^\s]+?\))+(?:\([^\s()]*?\([^\s()]+\)[^\s()]*?\)|\([^\s]+?\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’])|(?:(?<!@)[a-z0-9]+(?:[.\-][a-z0-9]+)*[.](?:com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)\b/?(?!@)))"""
URL_REGEX = r"""http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"""

# def get_match_and_replace(text):

# # for each in re.findall(URL_REGEX, text):
# # text = text.replace(each, '<a href="%s">%s</a>' % (each,each), 1)
# # return text # text but with anchors inserted

oembed_regex = re.compile(r'^(?P<spacing>\s*)(?P<url>http://.+)', re.MULTILINE)

@python_2_unicode_compatible
class _Abstract(models.Model): #microblog compatible.
slug = models.SlugField(unique=True)
title = models.CharField(max_length=140, unique=True)
text = models.TextField(default='')
rendered_text = models.TextField(default='', blank=True)
TEXT_TYPE = Choices(
(0, 'SIMPLE', 'Simple',),
(1, 'MARKDOWN', 'Markdown',),
(2, 'TEXTILE', 'Textile'),
)

def get_oembed_markup(self, matchobj):
gd = matchobj.groupdict('')
return '%(spacing)s<a href="%(url)s">%(url)s</a>' % gd
text_type = models.IntegerField(max_length=1, default=TEXT_TYPE.SIMPLE, choices=TEXT_TYPE)

def render(self):
#TODO: strip out dangerous HTML attributes, only allow basic formatting tags
self.rendered_text = oembed_regex.sub(self.get_oembed_markup, self.text)
def render(self, *args, **kwargs):

self.rendered_text = markdown.markdown(self.text)

if self.text_type == self.TEXT_TYPE.SIMPLE:
self.rendered_text = self.text

elif self.text_type == self.TEXT_TYPE.TEXTILE:
self.rendered_text = textile.textile(self.text)


soup = BeautifulSoup(self.rendered_text)

matching_text_nodes = soup.find_all(text = re.compile(URL_REGEX))

pyembed = PyEmbed()


for matching_text_node in matching_text_nodes:

plain_text = six.text_type(matching_text_node)

for each in re.findall(URL_REGEX, plain_text):
plain_text = plain_text.replace(each, '<a href="%s">%s</a>' % (each,each), 1)

if matching_text_node.parent.text == each: # if url on one line, each == url == matching_text_node
try:
oembed = pyembed.embed(matching_text_node)
except:
matching_text_node.replace_with(plain_text)
else:
matching_text_node.replace_with(oembed)
else:
matching_text_node.replace_with(plain_text)

soup = BeautifulSoup(soup.encode(formatter=None))

self.rendered_text = soup.encode(formatter=None).decode()

def save(self, *args, **kwargs):
if self.rendered_text == '':
self.render()

super(_Abstract, self).save(*args, **kwargs)
# try:
# ping_google()
# except Exception:
# # Bare 'except' because we could get a variety of HTTP-related exceptions.
# pass

def __str__(self):
return self.title
Expand Down Expand Up @@ -134,3 +182,58 @@ def get_absolute_url(self):

class Meta:
ordering = ['published']

class MockYoutube(models.Model):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you take this out?

ysc = models.IntegerField(max_length=3)
yu = models.CharField(max_length=140, unique=True)
yc = models.CharField(max_length=140, unique=True)
yt = models.TextField(default='')
yh = models.CharField(max_length=140, unique=True)

yosc = models.IntegerField(max_length=3)
you = models.CharField(max_length=140, unique=True)
yoh = models.CharField(max_length=140, unique=True)
yot = models.TextField(default='')
yoj = models.TextField(default='')

class Media(_Abstract):

MEDIA_TYPES = Choices(
(0, 'NONE', 'none',),
(1, 'IMAGE', 'image',),
(2, 'VIDEO', 'video',),
)

channel = models.ForeignKey(Channel)
objects = MediaQuerySet.as_manager()

upload_file = models.FileField(upload_to='uploads/%Y/%m/%d')

media_type = models.IntegerField(max_length=1, default=MEDIA_TYPES.NONE, choices=MEDIA_TYPES)

oembed_html = models.TextField(default='', blank=True)

media_height = models.SmallIntegerField(null=True)
media_width = models.SmallIntegerField(null=True)

thumbnail_height = models.SmallIntegerField(default=120)
thumbnail_width = models.SmallIntegerField(default=200)

@property
def file_url(self):
return self.upload_file.url

def render(self):

embed = '<a href="%s"> %s </a>' % (self.file_url, self.file_url)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move these 3 possible snippets to a single Django template (using if/else statements), render the template, and return the string? This will make it easier to customize the generated HTML if needed.


if self.media_type == Media.MEDIA_TYPES.IMAGE:
embed = '<img src="%s" alt="%s" style="width:%d%%; height:%d%%;">' % (self.file_url, self.title, self.media_width, self.media_height)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using the "style" attribute, could you use "width" and "height" attributes? Normally we'd use CSS, but images are a special case and it helps speed up the webpage rendering.


elif self.media_type == Media.MEDIA_TYPES.VIDEO:
embed = '<video width="%d" height="%d" controls> <source src="%s" type="video/mp4"> Your browser does not support the video tag. </video>' % (self.media_width, self.media_height, self.file_url)

self.oembed_html = embed

def get_absolute_url(self):
return reverse('mesh_media_view', kwargs={'slug': self.slug,})
2 changes: 1 addition & 1 deletion django_mesh/templates/django_mesh/base.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{% extends "base.html" %}
{% extends "base.html" %}
23 changes: 23 additions & 0 deletions django_mesh/templates/django_mesh/media_index_view.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% extends "django_mesh/base.html" %}

{% block content %}

<div class="media_list">
{% if media_list %}

{% for media in media_list %}
<div>
<a href="{% url 'mesh_media_view' slug=media.slug %}">{{ media.title|safe }}</a>
<div class="container2">

<p> <embed src="{{ media.file_url }}" autoplay="false" height="{{ media.thumbnail_height }}" width="{{ media.thumbnail_width }}">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the embed tag is non-standard and doesn't work in all browsers. Typically is placed inside an tag.

Does work for non-videos? Was the intent to have the actual file in the index or just a thumbnail image?

</p>
</div></div>
{% endfor %}

{% else %}
There are no files to display.
{% endif %}
</div>

{% endblock content %}
17 changes: 17 additions & 0 deletions django_mesh/templates/django_mesh/media_view.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{% extends "django_mesh/base.html" %}

{% block extra_head %}
<link rel="alternate" type="application/json" href="{% url 'mesh_oembed' slug=media.slug %}" title="{{ media.title }}"></link>
{% endblock %}

{% block content %}

<div class="media_list">
{% if Media.MEDIA_TYPES.IMAGE %}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to add "media.media_type == "


<a href="{{ media.file_url }}"> {{ media.oembed_html|safe }} </a>
{% else %}
{{ media.oembed_html | safe}}
{% endif %}

{% endblock content %}
2 changes: 1 addition & 1 deletion django_mesh/templates/django_mesh/post_view.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<h2>{{ post.title }}</h2>

{{ post.rendered_text }}
{{ post.rendered_text|safe }}
</div>
</div>
</div>
Expand Down
50 changes: 48 additions & 2 deletions django_mesh/tests/test_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#along with this program. If not, see <http://www.gnu.org/licenses/>.

# App imports
from ..models import Post, Channel, Tag
from ..models import Post, Channel, Tag, Media

# Test imports
from .util import BaseTestCase
Expand Down Expand Up @@ -288,4 +288,50 @@ def test_tags_only_show_up_if_user_has_access_to_that_Channel(self):
viewable = Tag.objects.get_for_user(new_user)

self.assertIn(self.t1, viewable)
self.assertNotIn(self.t2, viewable)
self.assertNotIn(self.t2, viewable)


class MediaQuerySetTestCase(BaseTestCase):
def test_get_for_user_with_a_user(self):
user = self.user

self.c1.save() # public

self.following_private_channel.save() # following private
self.following_private_channel.followers.add(user)

self.c3.save() # private channel that we are not following

self.f1.channel = self.c1
self.f1.save()

self.f2.channel = self.c3
self.f2.save()

self.f3.channel = self.following_private_channel
self.f3.save()

viewable = Media.objects.get_for_user(user)

self.assertIn(self.f1, viewable)
self.assertIn(self.f3, viewable)
self.assertNotIn(self.f2, viewable)


def test_get_for_user_anonymous(self):
user = self.user
user.id == None

self.c1.save()
self.c3.save()

self.f1.channel = self.c1
self.f2.channel = self.c3

self.f1.save()
self.f2.save()

viewable = Media.objects.get_for_user(user)

self.assertIn(self.f1, viewable)
self.assertNotIn(self.f2, viewable)
Loading