diff --git a/Makefile b/Makefile
index 00667ac..cdc9c09 100644
--- a/Makefile
+++ b/Makefile
@@ -10,6 +10,10 @@ help: ## This help.
run: ## Run the development environment from tox.
tox -e devenv
+.PHONY: shell
+shell: ## Run the django shell using some additional tools.
+ venv/bin/python manage.py shell_plus
+
.PHONY: migrate
migrate: makemigrations ## Run migrate on the DB, updating schema per migration files.
venv/bin/python manage.py migrate
@@ -37,9 +41,10 @@ update-flatpages: venv/bin/django-admin ## Update the flatpages from the markdow
collectstatic: ## Collect static files into the STATIC_ROOT directory.
venv/bin/python manage.py collectstatic
-.PHONY: check-deploy
-check-deploy: ## Run a check against the project for deployability.
+.PHONY: check
+check: ## Run various checks against the project
venv/bin/python manage.py check --deploy
+ venv/bin/python manage.py validate_templates
.PHONY: reestdb
resetdb: ## Remove the development database and regenerate it, loading fixtures.
@@ -91,14 +96,16 @@ test-travis: ## Test target for travis-ci use.
manage.py test --verbosity=2 $(TEST_SUITE)
coverage report -m --skip-covered
-.PHONY: sloccount
-sloccount: ## Get sloc count from all Python, html, markdown, Makefile, and shell files.
+.PHONY: metadata
+metadata: ## Get metadata about the project (sloc, models, urls)
git ls-files \
| grep -v static \
| grep -v manage.py \
| grep -v migrations \
| grep -E '(.py|.html|.md|Makefile|sh)' \
| xargs python sloc.py > sloc.tsv
+ venv/bin/python manage.py graph_models -a -o models.png
+ venv/bin/python manage.py show_urls > urls.tsv
.PHONY: clean
clean: ## Remove virtualenv and tox environments, along with compiled/optimized python files.
diff --git a/administration/flag_views.py b/administration/flag_views.py
index a8cc1ba..77c37f3 100644
--- a/administration/flag_views.py
+++ b/administration/flag_views.py
@@ -1,3 +1,4 @@
+from django.conf import settings
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import (
login_required,
@@ -67,14 +68,34 @@ def list_content_flags(request):
@login_required
def create_flag(request):
+ # Ensure that we have both content type and object id
if 'content_type' not in request.GET or 'object_id' not in request.GET:
return render(request, 'permission_denied.html', {
'title': 'Cannot create flag without a subject',
'additional_error': 'Flags must be related to an object',
}, status=403)
- ctype = get_object_or_404(ContentType, pk=request.GET.get('content_type'))
+
+ # Ensure that we can flag the given content type
+ if request.GET.get('content_type') not in \
+ settings.FLAGGABLE_CONTENT_TYPES:
+ return render(request, 'permission_denied.html', {
+ 'title': 'That content type is not flaggable',
+ 'additional_error':
+ 'The flaggable content types are
'.format(
+ ''.join([
+ ''+c+'' for c in
+ settings.FLAGGABLE_CONTENT_TYPES
+ ])
+ ),
+ }, status=403)
+
+ # Retrieve the content type and object
+ parts = request.GET.get('content_type').split(':')
+ ctype = get_object_or_404(ContentType, app_label=parts[0], model=parts[1])
obj = ctype.get_object_for_this_type(pk=request.GET.get('object_id'))
- if obj.owner == request.user:
+
+ # Ensure that we can flag the given object
+ if hasattr(obj, 'owner') and obj.owner == request.user:
return render(request, 'permission_denied.html', {
'title': 'Permission denied',
'additional_error': 'You cannot flag your own objects',
@@ -83,19 +104,24 @@ def create_flag(request):
content_type=ctype,
object_id=obj.id,
))
+
+ # Try to save any POSTed data
if request.method == 'POST':
form = FlagForm(request.POST)
if form.is_valid():
flag = form.save(commit=False)
flag.flagged_by = request.user
- flag.flagged_object_owner = flag.object_model.owner
+ flag.flagged_object_owner = (request.user if not
+ hasattr(flag.object_model, 'owner')
+ else flag.object_model.owner)
flag.save()
form.save_m2m()
flag.participants.add(request.user)
- flag.participants.add(flag.object_model.owner)
+ flag.participants.add(flag.flagged_object_owner)
return redirect(flag.get_absolute_url())
return render(request, 'create_flag.html', {
'title': 'Flag {}'.format(ctype.model),
+ 'subtitle': str(obj),
'form': form,
})
diff --git a/administration/templatetags/__init__.py b/administration/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/administration/templatetags/flag_extras.py b/administration/templatetags/flag_extras.py
new file mode 100644
index 0000000..91ff84b
--- /dev/null
+++ b/administration/templatetags/flag_extras.py
@@ -0,0 +1,19 @@
+from django import template
+
+from administration.models import Flag
+
+register = template.Library()
+
+
+@register.filter
+def can_view_flagged_item(user, flag):
+ """A filter for deciding if a user can view a flagged item."""
+ if user in flag.participants.all():
+ return True
+ if user.has_perm('administration.can_view_social_flags') and \
+ flag.flag_type == Flag.SOCIAL:
+ return True
+ if user.has_perm('administration.can_view_content_flags') and \
+ flag.flag_type == Flag.CONTENT:
+ return True
+ return False
diff --git a/core/templates/coverage-badge.svg b/core/templates/coverage-badge.svg
index fb84b7b..90da4ca 100644
--- a/core/templates/coverage-badge.svg
+++ b/core/templates/coverage-badge.svg
@@ -14,7 +14,7 @@
coverage
coverage
- 93%
- 93%
+ 92%
+ 92%
diff --git a/honeycomb/revno.py b/honeycomb/revno.py
index d56fa96..c25ecf2 100644
--- a/honeycomb/revno.py
+++ b/honeycomb/revno.py
@@ -1,2 +1,2 @@
-GIT_REVNO = 'e92c76811e1796f0ff6818a44405ca4335b95129'
+GIT_REVNO = 'cdf58cf04cd17c91fd5d981b43babd72e267764f'
VERSION = 'pre-release'
diff --git a/honeycomb/settings.py b/honeycomb/settings.py
index c9f6ef4..4a07d54 100644
--- a/honeycomb/settings.py
+++ b/honeycomb/settings.py
@@ -162,6 +162,7 @@
'taggit',
'haystack',
'datetimewidget',
+ 'django_extensions',
]
MIDDLEWARE = [
'honeycomb.middleware.BanMiddleware',
@@ -242,3 +243,14 @@
# NB you should also set this up on the server through apache/nginx config;
# this is only intended to be a backup
MAX_UPLOAD_SIZE = 1024 * 1024 * 10
+
+# The content types (in the form app_label:model) which users can flag for
+# administrative review.
+FLAGGABLE_CONTENT_TYPES = [
+ 'promotion:adlifecycle',
+ 'publishers:publisherpage',
+ 'social:comment',
+ 'submissions:submission',
+ 'taggit:tag',
+ 'usermgmt:profile',
+]
diff --git a/models.png b/models.png
new file mode 100644
index 0000000..7c33e9a
Binary files /dev/null and b/models.png differ
diff --git a/promotion/models.py b/promotion/models.py
index f9b031b..8bf027d 100644
--- a/promotion/models.py
+++ b/promotion/models.py
@@ -1,8 +1,10 @@
from __future__ import unicode_literals
from django.contrib.auth.models import User
+from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
+from administration.models import Flag
from submissions.models import Submission
@@ -86,3 +88,5 @@ class AdLifecycle(models.Model):
# Information about the ad's activity
impressions = models.PositiveIntegerField(default=0)
interactions = models.PositiveIntegerField(default=0)
+
+ flags = GenericRelation(Flag)
diff --git a/publishers/models.py b/publishers/models.py
index 2bad3a3..0416baa 100644
--- a/publishers/models.py
+++ b/publishers/models.py
@@ -1,8 +1,11 @@
from __future__ import unicode_literals
from django.contrib.auth.models import User
+from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
+from administration.models import Flag
+
class PublisherPage(models.Model):
"""A page on the site representing a publisher, collecting submissions by
@@ -23,3 +26,5 @@ class PublisherPage(models.Model):
# Users who have been published by the publisher
members = models.ManyToManyField(User)
+
+ flags = GenericRelation(Flag)
diff --git a/requirements.txt b/requirements.txt
index 19865e8..7d339a0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,30 +1,46 @@
+appnope==0.1.0
+backports.shutil-get-terminal-size==1.0.0
beautifulsoup4==4.5.1
bs4==0.0.1
configparser==3.5.0
coverage==4.0.3
coverage-badge==0.1.2
+decorator==4.0.10
Django==1.10.3
django-datetime-widget==0.9.3
+django-extensions==1.7.4
django-haystack==2.5.1
django-taggit==0.21.3
docutils==0.12
enum34==1.1.6
flake8==3.0.4
funcsigs==1.0.2
+ipython==5.1.0
+ipython-genutils==0.1.0
Markdown==2.6.7
mccabe==0.5.2
mock==2.0.0
+pathlib2==2.1.0
pbr==1.10.0
+pexpect==4.2.1
+pickleshare==0.7.4
Pillow==3.4.1
+prompt-toolkit==1.0.9
prose-wc==0.3.1
+ptyprocess==0.5.1
pycodestyle==2.0.0
pyflakes==1.2.3
Pygments==2.1.3
+pygraphviz==1.3.1
pymdown-extensions==1.1
pypandoc==1.3.3
python-coveralls==2.9.0
pytz==2016.7
PyYAML==3.12
requests==2.11.1
+simplegeneric==0.8.1
six==1.10.0
tblib==1.3.0
+traitlets==4.3.1
+wcwidth==0.1.7
+Werkzeug==0.11.11
diff --git a/sloc.tsv b/sloc.tsv
index a490ffd..58bb0a4 100644
--- a/sloc.tsv
+++ b/sloc.tsv
@@ -1,7 +1,7 @@
Count Location
44 CONTRIBUTING.md
13 DEPLOYING.md
- 104 Makefile
+ 110 Makefile
3 README.md
9 RELEASE.md
1 activitystream/__init__.py
@@ -17,7 +17,7 @@ Count Location
194 administration/application_views.py
4 administration/apps.py
123 administration/ban_views.py
- 183 administration/flag_views.py
+ 200 administration/flag_views.py
46 administration/forms.py
189 administration/models.py
106 administration/templates/admin-tabs-snippet.html
@@ -34,6 +34,8 @@ Count Location
76 administration/templates/view_application.html
45 administration/templates/view_ban.html
47 administration/templates/view_flag.html
+ 0 administration/templatetags/__init__.py
+ 15 administration/templatetags/flag_extras.py
533 administration/test_application.py
413 administration/test_ban.py
134 administration/test_flag.py
@@ -87,20 +89,20 @@ Count Location
0 honeycomb/__init__.py
17 honeycomb/middleware.py
2 honeycomb/revno.py
- 148 honeycomb/settings.py
+ 157 honeycomb/settings.py
20 honeycomb/urls.py
10 honeycomb/wsgi.py
104 honeycomb_markdown.py
0 promotion/__init__.py
0 promotion/admin.py
4 promotion/apps.py
- 58 promotion/models.py
+ 61 promotion/models.py
0 promotion/tests.py
0 promotion/views.py
0 publishers/__init__.py
0 publishers/admin.py
4 publishers/apps.py
- 13 publishers/models.py
+ 16 publishers/models.py
0 publishers/tests.py
0 publishers/views.py
7 run-coveralls.sh
@@ -109,7 +111,7 @@ Count Location
0 social/admin.py
4 social/apps.py
17 social/forms.py
- 64 social/models.py
+ 74 social/models.py
11 social/templates/confirm_unblock_user.html
123 social/templates/notification-snippet.html
237 social/templates/notifications_categories.html
@@ -123,38 +125,39 @@ Count Location
4 submissions/apps.py
238 submissions/folder_views.py
33 submissions/forms.py
- 212 submissions/models.py
+ 220 submissions/models.py
+ 49 submissions/templates/comment-body-snippet.html
24 submissions/templates/confirm_delete_folder.html
29 submissions/templates/confirm_delete_submission.html
15 submissions/templates/edit_folder.html
159 submissions/templates/edit_submission.html
11 submissions/templates/list_root_folders.html
102 submissions/templates/list_submissions.html
- 63 submissions/templates/subcomments-snippet.html
+ 44 submissions/templates/subcomments-snippet.html
21 submissions/templates/submission-list-snippet.html
38 submissions/templates/update_submission_order_in_folder.html
- 163 submissions/templates/view_submission.html
+ 249 submissions/templates/view_submission.html
478 submissions/test_folders.py
863 submissions/tests.py
48 submissions/urls.py
26 submissions/utils.py
- 276 submissions/views.py
+ 298 submissions/views.py
0 tags/__init__.py
4 tags/apps.py
12 tags/templates/list_tags.html
- 56 tags/templates/view_tag.html
+ 76 tags/templates/view_tag.html
0 tags/templatetags/__init__.py
48 tags/templatetags/tag_extras.py
116 tags/tests.py
27 tags/urls.py
- 59 tags/views.py
+ 78 tags/views.py
0 usermgmt/__init__.py
0 usermgmt/admin.py
4 usermgmt/apps.py
37 usermgmt/forms.py
10 usermgmt/group_models.py
119 usermgmt/group_views.py
- 159 usermgmt/models.py
+ 164 usermgmt/models.py
24 usermgmt/templates/confirm_delete_group.html
20 usermgmt/templates/list_groups.html
107 usermgmt/templates/profile-tabs-snippet.html
@@ -182,4 +185,4 @@ Count Location
24 usermgmt/urls.py
9 usermgmt/utils.py
87 usermgmt/views.py
-11534 TOTAL
+11787 TOTAL
diff --git a/social/models.py b/social/models.py
index 9925306..838818b 100644
--- a/social/models.py
+++ b/social/models.py
@@ -2,11 +2,15 @@
import markdown
from django.contrib.auth.models import User
-from django.contrib.contenttypes.fields import GenericForeignKey
+from django.contrib.contenttypes.fields import (
+ GenericForeignKey,
+ GenericRelation,
+)
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils.html import strip_tags
+from administration.models import Flag
from honeycomb_markdown import HoneycombMarkdown
from submissions.models import Submission
@@ -39,6 +43,8 @@ class Comment(models.Model):
deleted = models.BooleanField(default=False)
deleted_by_object_owner = models.BooleanField(default=False)
+ flags = GenericRelation(Flag)
+
def save(self, *args, **kwargs):
self.body_rendered = markdown.markdown(
strip_tags(self.body_raw),
@@ -54,6 +60,12 @@ def save(self, *args, **kwargs):
])
super(Comment, self).save(*args, **kwargs)
+ def get_active_flag(self):
+ """Retrieve flag if there is an active flag against this submission"""
+ active_flags = self.flags.filter(resolved=None)
+ if len(active_flags) > 0:
+ return active_flags[0]
+
def get_absolute_url(self):
return '{}#comment-{}'.format(
self.object_model.get_absolute_url(),
diff --git a/social/views.py b/social/views.py
index ff90b2f..e1a8281 100644
--- a/social/views.py
+++ b/social/views.py
@@ -294,7 +294,7 @@ def rate_submission(request, username=None, submission_id=None,
reader = request.user
author = submission.owner
- # MAke sure the rating is valid
+ # Make sure the rating is valid
try:
rating = int(request.POST.get('rating', 0))
except ValueError:
diff --git a/submissions/models.py b/submissions/models.py
index 383d85d..709f888 100644
--- a/submissions/models.py
+++ b/submissions/models.py
@@ -7,12 +7,14 @@
import tempfile
from django.contrib.auth.models import User
+from django.contrib.contenttypes.fields import GenericRelation
from django.core.urlresolvers import reverse
from django.db import models
from django.template.defaultfilters import slugify
from django.utils.html import strip_tags
from taggit.managers import TaggableManager
+from administration.models import Flag
from honeycomb_markdown import HoneycombMarkdown
from usermgmt.group_models import FriendGroup
@@ -126,6 +128,7 @@ class Submission(models.Model):
rating_count = models.PositiveIntegerField(default=0)
counts = models.CharField(max_length=250)
tags = TaggableManager()
+ flags = GenericRelation(Flag)
def save(self, *args, **kwargs):
"""Overridden save method.
@@ -220,6 +223,12 @@ def get_average_rating(self):
else:
return {'stars': '', 'average': 0, 'count': 0}
+ def get_active_flag(self):
+ """Retrieve flag if there is an active flag against this submission"""
+ active_flags = self.flags.filter(resolved=None)
+ if len(active_flags) > 0:
+ return active_flags[0]
+
def get_absolute_url(self):
"""Gets the absolute URL of the image, reversed from patterns."""
return reverse('submissions:view_submission', kwargs={
diff --git a/submissions/templates/comment-body-snippet.html b/submissions/templates/comment-body-snippet.html
new file mode 100644
index 0000000..e122bc5
--- /dev/null
+++ b/submissions/templates/comment-body-snippet.html
@@ -0,0 +1,49 @@
+{% load form_extras %}
+{% load gravatar %}
+{% load humanize %}
+
+
+ {{ comment.body_rendered|safe }}
+
+{% if can_reply and user.is_authenticated %}
+
+{% endif %}
diff --git a/submissions/templates/subcomments-snippet.html b/submissions/templates/subcomments-snippet.html
index 7621ec3..ad97460 100644
--- a/submissions/templates/subcomments-snippet.html
+++ b/submissions/templates/subcomments-snippet.html
@@ -1,63 +1,45 @@
-{% load form_extras %}
-{% load gravatar %}
-{% load humanize %}
+{% load flag_extras %}
{% for comment in comments %}
-