mirror of
https://github.com/nikdoof/vapemap.git
synced 2026-01-30 17:48:24 +00:00
Addition of content reporting
New app called "moderation" which handles the reporting, and in the future, management of incorrect records in the database.
This commit is contained in:
0
app/moderation/__init__.py
Normal file
0
app/moderation/__init__.py
Normal file
5
app/moderation/admin.py
Normal file
5
app/moderation/admin.py
Normal file
@@ -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)
|
||||||
95
app/moderation/migrations/0001_initial.py
Normal file
95
app/moderation/migrations/0001_initial.py
Normal file
@@ -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']
|
||||||
0
app/moderation/migrations/__init__.py
Normal file
0
app/moderation/migrations/__init__.py
Normal file
37
app/moderation/models.py
Normal file
37
app/moderation/models.py
Normal file
@@ -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)
|
||||||
1
app/moderation/templates/moderation/flag_button.html
Normal file
1
app/moderation/templates/moderation/flag_button.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<a class="btn btn-small" href="#report-modal" data-toggle="modal">Report an Issue</a>
|
||||||
81
app/moderation/templates/moderation/flag_form.html
Normal file
81
app/moderation/templates/moderation/flag_form.html
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<div id="report-modal" class="modal hide fade">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
|
<h3>Report {{ object }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="type">Type of Issue</label>
|
||||||
|
<div class="controls">
|
||||||
|
<select id="type">
|
||||||
|
{% for type in flagtypes %}
|
||||||
|
<option value="{{ type.pk }}">{{ type.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="note">Note</label>
|
||||||
|
<div class="controls">
|
||||||
|
<textarea id="note" rows=5 class="input-xlarge"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a id="report-button" href="#" class="btn btn-primary" onclick="submit_report()" data-loading-text="Submitting...">Report</a>
|
||||||
|
<script type="text/javascript">
|
||||||
|
function getCookie(name) {
|
||||||
|
var cookieValue = null;
|
||||||
|
if (document.cookie && document.cookie != '') {
|
||||||
|
var cookies = document.cookie.split(';');
|
||||||
|
for (var i = 0; i < cookies.length; i++) {
|
||||||
|
var cookie = jQuery.trim(cookies[i]);
|
||||||
|
// Does this cookie string begin with the name we want?
|
||||||
|
if (cookie.substring(0, name.length + 1) == (name + '=')) {
|
||||||
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cookieValue;
|
||||||
|
}
|
||||||
|
function bootstrap_alert(message, cls) {
|
||||||
|
$('#alert-container').append('<div class="alert alert-'+cls+'"><a class="close" data-dismiss="alert">×</a>'+message+'</div>')
|
||||||
|
}
|
||||||
|
function submit_report(){
|
||||||
|
$('#report-button').button('loading');
|
||||||
|
var data = {
|
||||||
|
app: '{{ contenttype.app_label }}',
|
||||||
|
model: '{{ contenttype.model }}',
|
||||||
|
id: {{ object.pk }},
|
||||||
|
flag_type: $('select#type').val(),
|
||||||
|
note: $('textarea#note').val()
|
||||||
|
}
|
||||||
|
console.log(data);
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '{% url "moderation_flagobject" %}',
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
}).done(function(data) {
|
||||||
|
bootstrap_alert('Report successfully created for the moderators to review', 'success');
|
||||||
|
$('#report-modal').modal('hide');
|
||||||
|
$('#report-button').button('reset');
|
||||||
|
console.log(data);
|
||||||
|
}).error(function(data){
|
||||||
|
console.log(data);
|
||||||
|
bootstrap_alert('An error was encountered while creating the report. Please try again later.', 'error');
|
||||||
|
$('#report-modal').modal('hide');
|
||||||
|
$('#report-button').button('reset');
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
1
app/moderation/templatetags/__init__.py
Normal file
1
app/moderation/templatetags/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
18
app/moderation/templatetags/moderation.py
Normal file
18
app/moderation/templatetags/moderation.py
Normal file
@@ -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 {}
|
||||||
132
app/moderation/tests.py
Normal file
132
app/moderation/tests.py
Normal file
@@ -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)
|
||||||
6
app/moderation/urls.py
Normal file
6
app/moderation/urls.py
Normal file
@@ -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'),
|
||||||
|
)
|
||||||
57
app/moderation/views.py
Normal file
57
app/moderation/views.py
Normal file
@@ -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)
|
||||||
164
app/stores/migrations/0006_add_moderation_types.py
Normal file
164
app/stores/migrations/0006_add_moderation_types.py
Normal file
@@ -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
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
{% load markdown_deux_tags %}
|
{% load markdown_deux_tags %}
|
||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
{% load waffle_tags %}
|
{% load waffle_tags %}
|
||||||
|
{% load moderation %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{ chain.name }}
|
{{ chain.name }}
|
||||||
@@ -49,6 +50,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<p>No description / write up available for this chain at the moment.</p>
|
<p>No description / write up available for this chain at the moment.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<p>{% flag_button %}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -74,4 +76,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% flag_form chain %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load markdown_deux_tags %}
|
{% load markdown_deux_tags %}
|
||||||
{% load waffle_tags %}
|
{% load waffle_tags %}
|
||||||
|
{% load moderation %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{ store.name }}
|
{{ store.name }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block style %}
|
{% block style %}
|
||||||
<style type="text/css">
|
<style type="text/css" xmlns="http://www.w3.org/1999/html">
|
||||||
.vcard ul {
|
.vcard ul {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
@@ -55,7 +56,9 @@ $(document).ready(function(){initialize_map_store()});
|
|||||||
{% else %}
|
{% else %}
|
||||||
<p>No description / write up available for this store at the moment.</p>
|
<p>No description / write up available for this store at the moment.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<p>{% flag_button %}</p>
|
||||||
|
|
||||||
{% if store.brands.count %}
|
{% if store.brands.count %}
|
||||||
<h3>Brands Stocked</h3>
|
<h3>Brands Stocked</h3>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -105,5 +108,6 @@ $(document).ready(function(){initialize_map_store()});
|
|||||||
</noscript>
|
</noscript>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% flag_form store %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -77,6 +77,7 @@ INSTALLED_APPS = [
|
|||||||
'bootstrapform',
|
'bootstrapform',
|
||||||
'registration',
|
'registration',
|
||||||
'haystack',
|
'haystack',
|
||||||
|
'moderation',
|
||||||
'stores',
|
'stores',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="alert-container">
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
|
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div{% if message.tags %} class="alert alert-{{ message.tags }}"{% endif %}>
|
<div{% if message.tags %} class="alert alert-{{ message.tags }}"{% endif %}>
|
||||||
<a class="close" data-dismiss="alert" href="#">×</a>
|
<a class="close" data-dismiss="alert" href="#">×</a>
|
||||||
@@ -63,6 +65,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ urlpatterns = patterns('',
|
|||||||
url(r'user/', include('registration.backends.default.urls')),
|
url(r'user/', include('registration.backends.default.urls')),
|
||||||
url(r'user/', include('django.contrib.auth.urls')),
|
url(r'user/', include('django.contrib.auth.urls')),
|
||||||
url(r'^search/', include('haystack.urls')),
|
url(r'^search/', include('haystack.urls')),
|
||||||
|
url(r'^moderation/', include('moderation.urls')),
|
||||||
url('', include('stores.urls'))
|
url('', include('stores.urls'))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ geopy>=0.95
|
|||||||
django-markdown-deux>=1.0.4
|
django-markdown-deux>=1.0.4
|
||||||
django-bootstrap-form>=1.4
|
django-bootstrap-form>=1.4
|
||||||
django-extra-views>=0.6.2
|
django-extra-views>=0.6.2
|
||||||
|
-e git+git://github.com/brack3t/django-braces.git@e0a8f04419eee0ca5809a62cffd3f5d1e80bb059#egg=django_braces
|
||||||
-e hg+https://bitbucket.org/alper/django-registration@1cddccb#egg=django_registration
|
-e hg+https://bitbucket.org/alper/django-registration@1cddccb#egg=django_registration
|
||||||
requests
|
requests
|
||||||
pyelasticsearch
|
pyelasticsearch
|
||||||
|
|||||||
Reference in New Issue
Block a user