Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upload #40

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
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)

8 changes: 8 additions & 0 deletions django_mesh/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
from django import forms

class UploadForm(forms.Form):
upload_file = forms.FileField(
label='Select a file',
help_text='max. 42 megabytes'
)
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()
136 changes: 121 additions & 15 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
from django.contrib.sites.models import Site

# 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 All @@ -80,6 +128,7 @@ class Channel(_Abstract):

objects = ChannelQuerySet.as_manager()


def get_absolute_url(self):
return reverse('mesh_channel_view', kwargs={'slug': self.slug,})

Expand Down Expand Up @@ -130,7 +179,64 @@ def _get_summary(self):
summary = property(_get_summary)

def get_absolute_url(self):
return reverse('mesh_post_view', kwargs={'slug': self.slug,})
return reverse('mesh_post_view', args=(self.slug,))

class Meta:
ordering = ['published']

class MockYoutube(models.Model):
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_TYPE = Choices(
Copy link
Member

Choose a reason for hiding this comment

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

Make plural

(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_TYPE.NONE, choices=MEDIA_TYPE)



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

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

def render(self):

embed = '<a href="'+self.get_file_url+'"> %s </a>' % self.get_file_url

if self.media_type == Media.MEDIA_TYPE.IMAGE:
embed = '<img src="'+self.get_file_url+'" style="width:100%;height:100%;>'

elif self.media_type == Media.MEDIA_TYPE.VIDEO:
embed = '<video width="700" height="350" controls> <source src="'+self.get_file_url+'" type="video/mp4"> Your browser does not support the video tag. </video>'

self.oembed_html = embed

def get_absolute_url(self):
return reverse('mesh_media_view', args=(self.slug,))

def save(self, *args, **kwargs):
self.render()
super(Media, self).save(*args, **kwargs)

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.get_file_url }}" autoplay="false" height="120" width="200">
</p>
</div></div>
{% endfor %}

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

{% endblock content %}
16 changes: 16 additions & 0 deletions django_mesh/templates/django_mesh/media_view.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% 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">
{% ifequal media.media_type 1 %}
<a href="{{ media.get_file_url }}"> {{ media.oembed_html | safe}}</a>
{% else %}
{{ media.oembed_html | safe}}
{% endifequal %}

{% 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