diff --git a/app/moderation/__init__.py b/app/moderation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/moderation/admin.py b/app/moderation/admin.py new file mode 100644 index 0000000..b5b4014 --- /dev/null +++ b/app/moderation/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from moderation.models import FlagType, FlaggedObject + +admin.site.register(FlagType, admin.ModelAdmin) +admin.site.register(FlaggedObject, admin.ModelAdmin) diff --git a/app/moderation/migrations/0001_initial.py b/app/moderation/migrations/0001_initial.py new file mode 100644 index 0000000..f6821d7 --- /dev/null +++ b/app/moderation/migrations/0001_initial.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'FlagType' + db.create_table(u'moderation_flagtype', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=200)), + ('is_active', self.gf('django.db.models.fields.BooleanField')(default=True)), + )) + db.send_create_signal(u'moderation', ['FlagType']) + + # Adding model 'FlaggedObject' + db.create_table(u'moderation_flaggedobject', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()), + ('object_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])), + ('flag_type', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['moderation.FlagType'])), + ('note', self.gf('django.db.models.fields.TextField')()), + ('status', self.gf('django.db.models.fields.PositiveIntegerField')(default=1)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='+', null=True, to=orm['auth.User'])), + )) + db.send_create_signal(u'moderation', ['FlaggedObject']) + + + def backwards(self, orm): + # Deleting model 'FlagType' + db.delete_table(u'moderation_flagtype') + + # Deleting model 'FlaggedObject' + db.delete_table(u'moderation_flaggedobject') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'moderation.flaggedobject': { + 'Meta': {'object_name': 'FlaggedObject'}, + 'flag_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': u"orm['moderation.FlagType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'object_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + 'status': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': u"orm['auth.User']"}) + }, + u'moderation.flagtype': { + 'Meta': {'object_name': 'FlagType'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + } + } + + complete_apps = ['moderation'] \ No newline at end of file diff --git a/app/moderation/migrations/__init__.py b/app/moderation/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/moderation/models.py b/app/moderation/models.py new file mode 100644 index 0000000..7858edc --- /dev/null +++ b/app/moderation/models.py @@ -0,0 +1,37 @@ +from django.conf import settings +from django.db import models +try: + from django.contrib.auth import get_user_model + USER_MODEL = get_user_model() +except ImportError: + from django.contrib.auth.models import User as USER_MODEL +from django.contrib.contenttypes import generic +from django.contrib.contenttypes.models import ContentType + + +DEFAULT_FLAGGING_STATUS = ( + (1, 'Reported'), + (2, 'Updated by Moderator'), + (3, 'Deleted by Moderator'), + (4, 'No Action'), +) +FLAGGING_STATUS = getattr(settings, 'FLAGGING_STATUS', DEFAULT_FLAGGING_STATUS) + + +class FlagType(models.Model): + name = models.CharField(max_length=200) + is_active = models.BooleanField(default=True) + + def __unicode__(self): + return self.name + + +class FlaggedObject(models.Model): + object_id = models.PositiveIntegerField() + object_type = models.ForeignKey(ContentType) + generic_obj = generic.GenericForeignKey('object_type', 'object_id') + + flag_type = models.ForeignKey('moderation.FlagType', related_name='+') + note = models.TextField() + status = models.PositiveIntegerField(choices=FLAGGING_STATUS, default=1) + user = models.ForeignKey(USER_MODEL, related_name='+', null=True, blank=True) \ No newline at end of file diff --git a/app/moderation/templates/moderation/flag_button.html b/app/moderation/templates/moderation/flag_button.html new file mode 100644 index 0000000..44dcda4 --- /dev/null +++ b/app/moderation/templates/moderation/flag_button.html @@ -0,0 +1 @@ +Report an Issue \ No newline at end of file diff --git a/app/moderation/templates/moderation/flag_form.html b/app/moderation/templates/moderation/flag_form.html new file mode 100644 index 0000000..753d90c --- /dev/null +++ b/app/moderation/templates/moderation/flag_form.html @@ -0,0 +1,81 @@ + \ No newline at end of file diff --git a/app/moderation/templatetags/__init__.py b/app/moderation/templatetags/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/app/moderation/templatetags/__init__.py @@ -0,0 +1 @@ + diff --git a/app/moderation/templatetags/moderation.py b/app/moderation/templatetags/moderation.py new file mode 100644 index 0000000..956997b --- /dev/null +++ b/app/moderation/templatetags/moderation.py @@ -0,0 +1,18 @@ +from django import template +from django.contrib.contenttypes.models import ContentType +from ..models import FlagType + +register = template.Library() + +@register.inclusion_tag('moderation/flag_form.html') +def flag_form(obj): + return { + 'flagtypes': FlagType.objects.filter(is_active=True), + 'object': obj, + 'contenttype': ContentType.objects.get_for_model(obj), + } + + +@register.inclusion_tag('moderation/flag_button.html') +def flag_button(): + return {} \ No newline at end of file diff --git a/app/moderation/tests.py b/app/moderation/tests.py new file mode 100644 index 0000000..528d169 --- /dev/null +++ b/app/moderation/tests.py @@ -0,0 +1,132 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +import json +from django.test import TestCase +from django.core.urlresolvers import reverse +try: + from django.contrib.auth import get_user_model + USER_MODEL = get_user_model() +except ImportError: + from django.contrib.auth.models import User as USER_MODEL +from moderation.models import FlagType, FlaggedObject + + +class FlagObjectViewTestCase(TestCase): + + def setUp(self): + self.u1 = USER_MODEL.objects.create_user('user1', 'test@test.com', 'user1') + self.f1 = FlagType.objects.create(name='Test') + + def tearDown(self): + self.u1.delete() + self.f1.delete() + + def post_to_flagobject(self, data={}): + return self.client.post(reverse('moderation_flagobject'), data, content_type='application/json', HTTP_X_REQUESTED_WITH='XMLHttpRequest') + + + def test_invalid_json(self): + resp = self.post_to_flagobject('') + self.assertEqual(resp.status_code, 400) + content = json.loads(resp.content) + self.assertEqual(content['result'], 'invalid-request') + self.assertEqual(content['reason'], 'invalid-request') + + def test_invalid_json_2(self): + resp = self.post_to_flagobject({'data': '1234567890'}) + self.assertEqual(resp.status_code, 400) + content = json.loads(resp.content) + self.assertEqual(content['result'], 'invalid-request') + self.assertEqual(content['reason'], 'invalid-json') + + def test_missing_app_model_json(self): + resp = self.post_to_flagobject(json.dumps({'invalid': 'invalid'})) + self.assertEqual(resp.status_code, 400) + content = json.loads(resp.content) + self.assertEqual(content['result'], 'invalid-request') + self.assertEqual(content['reason'], 'no-app-or-model') + + def test_unknown_type(self): + resp = self.post_to_flagobject(json.dumps({ + 'app': 'moderation', + 'model': 'xxxxxxxxxxxxxxxxxxxx', + 'id': 999999, + 'flag_type': 1, + 'note': 'test note', + })) + self.assertEqual(resp.status_code, 400) + content = json.loads(resp.content) + self.assertEqual(content['result'], 'invalid-request') + self.assertEqual(content['reason'], 'unknown-object-type') + + def test_missing_object(self): + resp = self.post_to_flagobject(json.dumps({ + 'app': 'moderation', + 'model': 'flaggedobject', + 'id': 999999, + 'flag_type': 1, + 'note': 'test note', + })) + self.assertEqual(resp.status_code, 400) + content = json.loads(resp.content) + self.assertEqual(content['result'], 'invalid-request') + self.assertEqual(content['reason'], 'does-not-exist') + + def test_missing_fields(self): + resp = self.post_to_flagobject(json.dumps({ + 'app': 'moderation', + 'model': 'flaggedobject', + 'id': 999999, + 'flag_type': 1, + })) + self.assertEqual(resp.status_code, 400) + content = json.loads(resp.content) + self.assertEqual(content['result'], 'invalid-request') + self.assertEqual(content['reason'], 'missing-fields') + + def test_flagging_authenticated(self): + + self.client.login(username='user1', password='user1') + + resp = self.post_to_flagobject(json.dumps({ + 'app': 'moderation', + 'model': 'flagtype', + 'id': self.f1.pk, + 'flag_type': 1, + 'note': 'test note', + })) + self.assertEqual(resp.status_code, 200) + content = json.loads(resp.content) + self.assertEqual(content['result'], 'ok') + self.assertIsNotNone(content['flag_id']) + + flag = FlaggedObject.objects.get(pk=content['flag_id']) + self.assertIsNotNone(flag) + self.assertEqual(flag.user, self.u1) + self.assertEqual(flag.flag_type, self.f1) + + def test_flagging_unauthenticated(self): + + self.client.logout() + + resp = self.post_to_flagobject(json.dumps({ + 'app': 'moderation', + 'model': 'flagtype', + 'id': self.f1.pk, + 'flag_type': 1, + 'note': 'test note', + })) + self.assertEqual(resp.status_code, 200) + content = json.loads(resp.content) + self.assertEqual(content['result'], 'ok') + self.assertIsNotNone(content['flag_id']) + + flag = FlaggedObject.objects.get(pk=content['flag_id']) + self.assertIsNotNone(flag) + self.assertEqual(flag.user, None) + self.assertEqual(flag.flag_type, self.f1) \ No newline at end of file diff --git a/app/moderation/urls.py b/app/moderation/urls.py new file mode 100644 index 0000000..1b19c5d --- /dev/null +++ b/app/moderation/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls import patterns, url +from moderation.views import FlagObjectView + +urlpatterns = patterns('', + url(r'^flag/$', FlagObjectView.as_view(), name='moderation_flagobject'), +) \ No newline at end of file diff --git a/app/moderation/views.py b/app/moderation/views.py new file mode 100644 index 0000000..0488c6e --- /dev/null +++ b/app/moderation/views.py @@ -0,0 +1,57 @@ +import json +from django.http import HttpResponseBadRequest +from django.views.generic import View +from django.contrib.contenttypes.models import ContentType +from braces.views import AjaxResponseMixin, JSONResponseMixin +from moderation.models import FlaggedObject + + +class FlagObjectView(JSONResponseMixin, AjaxResponseMixin, View): + """ + Ajax capable view to flag a object for moderation + """ + + def render_invalid_response(self, reason='invalid-request'): + return HttpResponseBadRequest(content=json.dumps({ + 'result': 'invalid-request', + 'reason': reason, + }), content_type=self.get_content_type()) + + def post(self, request, *args, **kwargs): + pass + + def post_ajax(self, request, *args, **kwargs): + data = request.raw_post_data + if not data: + return self.render_invalid_response() + try: + data = json.loads(data) + except ValueError: + return self.render_invalid_response('invalid-json') + if not isinstance(data, dict): + return self.render_invalid_response('invalid-json') + if not 'app' in data or not 'model' in data: + return self.render_invalid_response('no-app-or-model') + if not 'flag_type' in data or not 'note' in data: + return self.render_invalid_response('missing-fields') + try: + ct = ContentType.objects.get(app_label=data['app'], model=data['model']) + except ContentType.DoesNotExist: + return self.render_invalid_response('unknown-object-type') + cls = ct.model_class() + try: + cls.objects.get(pk=data['id']) + except cls.DoesNotExist: + return self.render_invalid_response('does-not-exist') + if request.user.is_authenticated(): + user = request.user + else: + user = None + flag = FlaggedObject(object_type=ct, object_id=data['id'], flag_type_id=data['flag_type'], + note=data['note'], user=user) + flag.save() + result = { + 'result': 'ok', + 'flag_id': flag.pk, + } + return self.render_json_response(result) \ No newline at end of file diff --git a/app/stores/migrations/0006_add_moderation_types.py b/app/stores/migrations/0006_add_moderation_types.py new file mode 100644 index 0000000..d776f66 --- /dev/null +++ b/app/stores/migrations/0006_add_moderation_types.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + depends_on = ( + ('moderation', '0001_initial'), + ) + + def forwards(self, orm): + orm['moderation.FlagType'].objects.create(name='Incorrect address') + orm['moderation.FlagType'].objects.create(name='Incorrect contact details') + orm['moderation.FlagType'].objects.create(name='Does not exist') + orm['moderation.FlagType'].objects.create(name='Owner request') + orm['moderation.FlagType'].objects.create(name='Wrong store type') + + def backwards(self, orm): + pass + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'moderation.flaggedobject': { + 'Meta': {'object_name': 'FlaggedObject'}, + 'flag_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': u"orm['moderation.FlagType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'object_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + 'status': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': u"orm['auth.User']"}) + }, + u'moderation.flagtype': { + 'Meta': {'object_name': 'FlagType'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + 'stores.address': { + 'Meta': {'object_name': 'Address'}, + 'address1': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'address2': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'address3': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'city': ('django.db.models.fields.CharField', [], {'max_length': "'50'"}), + 'country': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'addresses'", 'to': "orm['stores.Country']"}), + 'county': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'addresses'", 'null': 'True', 'to': "orm['stores.County']"}), + 'geo_latitude': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'geo_longitude': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'postcode': ('django.db.models.fields.CharField', [], {'max_length': '20'}) + }, + 'stores.brand': { + 'Meta': {'ordering': "['name']", 'object_name': 'Brand'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}) + }, + 'stores.chain': { + 'Meta': {'ordering': "['name']", 'object_name': 'Chain'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'editor': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'editable_chains'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'head_office': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['stores.Address']", 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'long_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '200', 'blank': 'True'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}) + }, + 'stores.claimrequest': { + 'Meta': {'object_name': 'ClaimRequest'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'object_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claims'", 'to': u"orm['auth.User']"}) + }, + 'stores.country': { + 'Meta': {'ordering': "['name']", 'object_name': 'Country'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'iso_code': ('django.db.models.fields.CharField', [], {'max_length': '3'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'stores.county': { + 'Meta': {'ordering': "['name']", 'object_name': 'County'}, + 'country': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'counties'", 'null': 'True', 'to': "orm['stores.Country']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'stores.link': { + 'Meta': {'object_name': 'Link'}, + 'account_name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'account_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['stores.LinkType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'object_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}) + }, + 'stores.linktype': { + 'Meta': {'ordering': "['name']", 'object_name': 'LinkType'}, + 'icon': ('django.db.models.fields.CharField', [], {'default': "'icon-globe'", 'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'url_format': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'stores.store': { + 'Meta': {'ordering': "['name']", 'object_name': 'Store'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'address': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'stores'", 'to': "orm['stores.Address']"}), + 'brands': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['stores.Brand']", 'null': 'True', 'blank': 'True'}), + 'chain': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'stores'", 'null': 'True', 'to': "orm['stores.Chain']"}), + 'editor': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'editable_stores'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'long_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'phone': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '200', 'blank': 'True'}), + 'store_type': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['moderation', 'stores'] + symmetrical = True diff --git a/app/stores/templates/stores/chain_detail.html b/app/stores/templates/stores/chain_detail.html index 8fed5d5..f15dfad 100644 --- a/app/stores/templates/stores/chain_detail.html +++ b/app/stores/templates/stores/chain_detail.html @@ -2,6 +2,7 @@ {% load markdown_deux_tags %} {% load staticfiles %} {% load waffle_tags %} +{% load moderation %} {% block title %} {{ chain.name }} @@ -49,6 +50,7 @@ {% else %}

No description / write up available for this chain at the moment.

{% endif %} +

{% flag_button %}

@@ -74,4 +76,5 @@ + {% flag_form chain %} {% endblock %} \ No newline at end of file diff --git a/app/stores/templates/stores/store_detail.html b/app/stores/templates/stores/store_detail.html index 44e6665..f6226be 100644 --- a/app/stores/templates/stores/store_detail.html +++ b/app/stores/templates/stores/store_detail.html @@ -1,13 +1,14 @@ {% extends "base.html" %} {% load markdown_deux_tags %} {% load waffle_tags %} +{% load moderation %} {% block title %} {{ store.name }} {% endblock %} {% block style %} -