mirror of
https://github.com/postgres/pgweb.git
synced 2025-08-13 13:12:42 +00:00
Re-work moderation of submitted items
This includes a number of new features: * Move some moderation functionality into shared places, so we don't keep re-inventing the wheel. * Implement three-state moderation, where the submitter can edit their item and then explicitly say "i'm done, please moderate this now". This is currently only implemented for News, but done in a reusable way. * Move moderation workflow to it's own set of URLs instead of overloading it on the general admin interface. Admin interface remains for editing things, but these are now separated out into separate things. * Do proper stylesheet clearing for moderation of markdown fields, using a dynamic sandboxed iframe, so it's not ruined by the /admin/ css. * Move moderation email notification into dedicated moderation code, thereby simplifying the admin subclassing we did which was in some places quite fragile. * Reset date of news postings to the date of their approval, when approved. This avoids some annoying ordering issues.
This commit is contained in:
@ -1,30 +1,161 @@
|
||||
# models needed to generate unapproved list
|
||||
from pgweb.news.models import NewsArticle
|
||||
from pgweb.events.models import Event
|
||||
from pgweb.core.models import Organisation
|
||||
from pgweb.downloads.models import Product
|
||||
from pgweb.profserv.models import ProfessionalService
|
||||
from pgweb.quotes.models import Quote
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
import datetime
|
||||
|
||||
import markdown
|
||||
|
||||
|
||||
class ModerateModel(models.Model):
|
||||
def _get_field_data(self, k):
|
||||
val = getattr(self, k)
|
||||
yield k
|
||||
|
||||
try:
|
||||
yield self._meta.get_field(k).verbose_name.capitalize()
|
||||
except Exception:
|
||||
yield k.capitalize()
|
||||
yield val
|
||||
|
||||
if k in getattr(self, 'markdown_fields', []):
|
||||
yield markdown.markdown(val)
|
||||
else:
|
||||
yield None
|
||||
|
||||
if k == 'date' and isinstance(val, datetime.date):
|
||||
yield "Will be reset to today's date when this {} is approved".format(self._meta.verbose_name)
|
||||
else:
|
||||
yield None
|
||||
|
||||
def get_preview_fields(self):
|
||||
if getattr(self, 'preview_fields', []):
|
||||
return [list(self._get_field_data(k)) for k in self.preview_fields]
|
||||
return self.get_moderation_preview_fields()
|
||||
|
||||
def get_moderation_preview_fields(self):
|
||||
return [list(self._get_field_data(k)) for k in self.moderation_fields]
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@property
|
||||
def block_edit(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def twomoderators(self):
|
||||
return hasattr(self, 'firstmoderator')
|
||||
|
||||
def twomoderators_string(self):
|
||||
return None
|
||||
|
||||
|
||||
class ModerationState(object):
|
||||
CREATED = 0
|
||||
PENDING = 1
|
||||
APPROVED = 2
|
||||
REJECTED = -1 # Never stored, so not available as a choice
|
||||
|
||||
CHOICES = (
|
||||
(CREATED, 'Created (submitter edits)'),
|
||||
(PENDING, 'Pending moderation'),
|
||||
(APPROVED, 'Approved and published'),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_string(cls, modstate):
|
||||
return next(filter(lambda x: x[0] == modstate, cls.CHOICES))[1]
|
||||
|
||||
|
||||
class TristateModerateModel(ModerateModel):
|
||||
modstate = models.IntegerField(null=False, blank=False, default=0, choices=ModerationState.CHOICES,
|
||||
verbose_name="Moderation state")
|
||||
|
||||
send_notification = True
|
||||
send_m2m_notification = True
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@property
|
||||
def modstate_string(self):
|
||||
return ModerationState.get_string(self.modstate)
|
||||
|
||||
@property
|
||||
def is_approved(self):
|
||||
return self.modstate == ModerationState.APPROVED
|
||||
|
||||
|
||||
class TwostateModerateModel(ModerateModel):
|
||||
approved = models.BooleanField(null=False, blank=False, default=False)
|
||||
|
||||
send_notification = True
|
||||
send_m2m_notification = True
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@property
|
||||
def modstate_string(self):
|
||||
return self.approved and 'Approved' or 'Created/Pending'
|
||||
|
||||
@property
|
||||
def modstate(self):
|
||||
return self.approved and ModerationState.APPROVED or ModerationState.CREATED
|
||||
|
||||
@property
|
||||
def is_approved(self):
|
||||
return self.approved
|
||||
|
||||
|
||||
class TwoModeratorsMixin(models.Model):
|
||||
firstmoderator = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def twomoderators_string(self):
|
||||
if self.firstmoderator:
|
||||
return "Already approved by {}, waiting for second moderator".format(self.firstmoderator)
|
||||
return "Requires two moderators, not approved by anybody yet"
|
||||
|
||||
|
||||
# Pending moderation requests (including URLs for the admin interface))
|
||||
def _get_unapproved_list(objecttype):
|
||||
objects = objecttype.objects.filter(approved=False)
|
||||
if hasattr(objecttype, 'approved'):
|
||||
objects = objecttype.objects.filter(approved=False)
|
||||
else:
|
||||
objects = objecttype.objects.filter(modstate=ModerationState.PENDING)
|
||||
if not len(objects):
|
||||
return None
|
||||
return {
|
||||
'name': objects[0]._meta.verbose_name_plural,
|
||||
'entries': [{'url': '/admin/%s/%s/%s/' % (x._meta.app_label, x._meta.model_name, x.pk), 'title': str(x)} for x in objects]
|
||||
'entries': [
|
||||
{
|
||||
'url': '/admin/_moderate/%s/%s/' % (x._meta.model_name, x.pk),
|
||||
'title': str(x),
|
||||
'twomoderators': x.twomoderators_string(),
|
||||
} for x in objects]
|
||||
}
|
||||
|
||||
|
||||
def _modclasses():
|
||||
from pgweb.news.models import NewsArticle
|
||||
from pgweb.events.models import Event
|
||||
from pgweb.core.models import Organisation
|
||||
from pgweb.downloads.models import Product
|
||||
from pgweb.profserv.models import ProfessionalService
|
||||
return [NewsArticle, Event, Organisation, Product, ProfessionalService]
|
||||
|
||||
|
||||
def get_all_pending_moderations():
|
||||
applist = [
|
||||
_get_unapproved_list(NewsArticle),
|
||||
_get_unapproved_list(Event),
|
||||
_get_unapproved_list(Organisation),
|
||||
_get_unapproved_list(Product),
|
||||
_get_unapproved_list(ProfessionalService),
|
||||
_get_unapproved_list(Quote),
|
||||
]
|
||||
applist = [_get_unapproved_list(c) for c in _modclasses()]
|
||||
return [x for x in applist if x]
|
||||
|
||||
|
||||
def get_moderation_model(modelname):
|
||||
return next((c for c in _modclasses() if c._meta.model_name == modelname))
|
||||
|
||||
|
||||
def get_moderation_model_from_suburl(suburl):
|
||||
return next((c for c in _modclasses() if c.account_edit_suburl == suburl))
|
||||
|
Reference in New Issue
Block a user