Commit 40f1342b authored by Dylann Cordel's avatar Dylann Cordel

send email and postman messages

parent 51984145
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core import mail
from admin_message.models import RecipientMixin from collections import OrderedDict
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, User
from django.core import mail
def send_messages(sender, subject, recipients, txt, html=None): def send_messages(sender, subject, recipients, txt, html=None):
...@@ -29,25 +32,40 @@ def clean_recipient_name(name): ...@@ -29,25 +32,40 @@ def clean_recipient_name(name):
return name.replace('"', '').strip() return name.replace('"', '').strip()
def get_final_recipients_from_misc(obj): def get_senders(user):
"""
Returns an OrderedDict of available identities to use as "sender".
Email must be used as key and display name as value
"""
senders = OrderedDict()
if user:
senders[user.email] = "%s %s" % (user.first_name, user.last_name)
return senders
def get_default_message(user):
return ''
def get_final_recipients(obj):
""" """
Returns a list of valid email recipient: Returns a OrderedDict of valid email recipient:
`"display name" <email>` email as key and
or just `email` if there is not any display name available `"display name" <email>` or just `email` as value if there
is not any display name available
""" """
user_model = get_user_model() user_model = get_user_model()
recipients = [] recipients = OrderedDict()
if isinstance(obj, user_model): if isinstance(obj, (user_model, User)):
email_field = (user_model.get_email_field_name() email_field = (user_model.get_email_field_name()
if hasattr(user_model, 'get_email_field_name') if hasattr(user_model, 'get_email_field_name')
else 'email') else 'email')
email = getattr(obj, email_field) email = getattr(obj, email_field)
if email if email:
if hasattr(obj, 'get_full_name'): full_name = (clean_recipient_name(obj.get_full_name())
full_name = RecipientMixin.clean_recipient_name(obj.get_full_name()) if hasattr(obj, 'get_full_name') else None)
if full_name: recipients[email] = '"%s" <%s>' % (full_name, email) if full_name else email
return recipients.append('"%s" <%s>' % (full_name, email))
return recipients.append(email)
elif isinstance(obj, Group): elif isinstance(obj, Group):
user_model = obj.user_set.model user_model = obj.user_set.model
email_field = (user_model.get_email_field_name() email_field = (user_model.get_email_field_name()
...@@ -57,7 +75,7 @@ def get_final_recipients_from_misc(obj): ...@@ -57,7 +75,7 @@ def get_final_recipients_from_misc(obj):
email = getattr(user, email_field) email = getattr(user, email_field)
if not email: if not email:
continue continue
full_name = (RecipientMixin.clean_recipient_name(obj.get_full_name()) full_name = (clean_recipient_name(obj.get_full_name())
if hasattr(obj, 'get_full_name') else '') if hasattr(obj, 'get_full_name') else '')
recipients.append('"%s" <%s>' % (full_name, email) if full_name else email) recipients[email] = '"%s" <%s>' % (full_name, email) if full_name else email
return recipients return recipients
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import absolute_import
from collections import OrderedDict
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from postman.api import pm_broadcast
def send_messages(sender, subject, recipients, txt, html=None, extra_form_data=None):
user_model = get_user_model()
sender = user_model.objects.get(pk=sender)
recipients = [u for u in user_model.objects.filter(pk__in=recipients.keys())]
skip_notification = extra_form_data.get('skip_notification', False)
pm_broadcast(sender, recipients, subject, txt, skip_notification)
def clean_recipient_name(name):
if name:
return name.replace('"', '').strip()
def get_senders(user):
"""
Returns an OrderedDict of available identities to use as "sender".
User.pk must be used as key and display name as value
"""
senders = OrderedDict()
if user:
senders[user.pk] = "%s %s" % (user.first_name, user.last_name)
return senders
def get_default_message(user):
return ''
def get_final_recipients(obj):
"""
Returns an OrderedDict of valid users pk recipients:
"pk" as key
and `"display name" <username:pk>`
or just `username:pk` if there is not any display name available
"""
user_model = get_user_model()
recipients = OrderedDict()
if isinstance(obj, user_model):
full_name = (clean_recipient_name(obj.get_full_name())
if hasattr(obj, 'get_full_name') else None)
if full_name:
recipients[obj.pk] = '"%s" <%s:%d>' % (full_name, obj.get_username(), obj.pk)
else:
recipients[obj.pk] = '%s:%d' % (obj.get_username(), obj.pk)
elif isinstance(obj, Group):
user_model = obj.user_set.model
for user in obj.user_set.filter(active=True):
full_name = (clean_recipient_name(obj.get_full_name())
if hasattr(obj, 'get_full_name') else '')
if full_name:
recipients[obj.pk] = '"%s" <%s:%d>' % (full_name, user.get_username(), user.pk)
else:
recipients[obj.pk] = '%s:%d' % (user.get_username(), user.pk)
return recipients
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from collections import OrderedDict
from datetime import datetime from datetime import datetime
import html2text from html2text import HTML2Text
from django.contrib import admin from django.contrib import admin
from django.contrib import messages from django.contrib import messages
...@@ -13,7 +14,6 @@ from django.template.response import TemplateResponse ...@@ -13,7 +14,6 @@ from django.template.response import TemplateResponse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from admin_message.models import Message from admin_message.models import Message
# from admin_message.models import RecipientMixin
from admin_message.forms import SendMessageForm from admin_message.forms import SendMessageForm
from admin_message.conf import app_settings from admin_message.conf import app_settings
...@@ -27,23 +27,23 @@ class SendMessageAdminMixin(object): ...@@ -27,23 +27,23 @@ class SendMessageAdminMixin(object):
intermédiaire pour tester et écrire le message. intermédiaire pour tester et écrire le message.
""" """
# def __init__(self, *args, **kwargs):
# super(SendMessageAdminMixin, self).__init__(*args, **kwargs)
# if not issubclass(self.model, RecipientMixin):
# raise Exception('%s model is not a subclass of RecipientMixin' % self.model)
def get_actions(self, request): def get_actions(self, request):
actions = super(SendMessageAdminMixin, self).get_actions(request) actions = super(SendMessageAdminMixin, self).get_actions(request)
if 'send_message' not in actions: for message_type, infos in app_settings.MESSAGE_TYPES.items():
actions['send_message'] = self.get_action('send_message') action_code = 'admin_message_send_%s' % message_type
if action_code not in actions:
actions[action_code] = (self.get_action('_admin_message_send')[0],
action_code, infos['label'])
return actions return actions
def get_sendmessage_form(self, request, queryset): def _admin_message_get_sendmessage_form(self, request, queryset, message_type):
data = request.POST if 'send_message' in request.POST else None data = request.POST if 'send_message' in request.POST else None
form = SendMessageForm(queryset, request.user, data) form = SendMessageForm(queryset, request.user, message_type, data)
return form return form
def send_message(self, request, queryset): def _admin_message_send(self, request, queryset):
action = request.POST['action']
message_type = action.replace('admin_message_send_', '')
opts = self.model._meta opts = self.model._meta
app_label = opts.app_label app_label = opts.app_label
...@@ -53,27 +53,27 @@ class SendMessageAdminMixin(object): ...@@ -53,27 +53,27 @@ class SendMessageAdminMixin(object):
# The user validated the form # The user validated the form
if request.method == 'POST' and 'send_message' in request.POST: if request.method == 'POST' and 'send_message' in request.POST:
form = self.get_sendmessage_form(request, queryset) form = self._admin_message_get_sendmessage_form(request, queryset, message_type)
if form.is_valid(): if form.is_valid():
sender = form.cleaned_data['sender'] kwargs = {
message = form.cleaned_data['message'] 'request': request,
objects = form.cleaned_data['objects'] 'objects': form.cleaned_data['objects'],
'subject': form.cleaned_data['subject'],
'sender': form.cleaned_data['sender'],
'message': form.cleaned_data['message'],
'message_type': message_type,
}
kwargs['extra_form_data'] = dict(set(form.cleaned_data) - set(kwargs))
if 'test' in request.POST: if 'test' in request.POST:
test_email = form.cleaned_data['sender'] kwargs['test_recipient'] = form.cleaned_data['sender']
self._send_message(request, objects, self._admin_message_send_message(**kwargs)
form.cleaned_data['subject'],
test_email=test_email,
sender=sender, message=message)
else: else:
self._send_message(request, objects, self._admin_message_send_message(**kwargs)
form.cleaned_data['subject'],
sender=sender, message=message)
# Return None to display the change list page again. # Return None to display the change list page again.
return None return None
else: else:
form = self.get_sendmessage_form(request, queryset) form = self._admin_message_get_sendmessage_form(request, queryset, message_type)
if len(queryset) == 1: if len(queryset) == 1:
objects_name = opts.verbose_name objects_name = opts.verbose_name
...@@ -90,23 +90,20 @@ class SendMessageAdminMixin(object): ...@@ -90,23 +90,20 @@ class SendMessageAdminMixin(object):
'opts': opts, 'opts': opts,
'app_label': app_label, 'app_label': app_label,
'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME, 'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME,
'action': 'send_message', 'action': action,
} }
# Display the confirmation page # Display the confirmation page
return TemplateResponse( return TemplateResponse(
request, request,
'srn/admin/send_message.html', 'admin_message/admin/send_message.html',
context, context,
current_app=self.admin_site.name, current_app=self.admin_site.name,
) )
send_message.short_description = 'Envoyer un message'
def _get_message_body(self, template, message, context={}): def _admin_message_get_message_body(self, template, message, context={}):
if message: if message:
h2t = html2text.HTML2Text() h2t = HTML2Text()
# Ignore converting links from HTML
h2t.ignore_links = True
txt_message = h2t.handle(message) txt_message = h2t.handle(message)
else: else:
txt_message = '' txt_message = ''
...@@ -120,35 +117,36 @@ class SendMessageAdminMixin(object): ...@@ -120,35 +117,36 @@ class SendMessageAdminMixin(object):
html = None html = None
return (txt, html) return (txt, html)
def _send_message( def _admin_message_send_message(
self, request, objects, subject, message_type, test_recipient=None, self,
sender=None, message=None request, objects, subject, message_type,
test_recipient=None, sender=None, message=None, extra_form_data=None
): ):
if test_recipient: if test_recipient:
objects = objects[:1] objects = objects[:1]
template = app_settings.MESSAGE_TYPES[message_type]['template'] template = app_settings.MESSAGE_TYPES[message_type]['template']
(txt, html) = self._get_message_body(template, message, context={'subject': subject, get_final_recipients = app_settings.MESSAGE_TYPES[message_type]['get_final_recipients']
'sender': sender}) (txt, html) = self._admin_message_get_message_body(template, message,
context={'subject': subject,
'sender': sender})
recipients = [] recipients = OrderedDict()
for obj in objects: for obj in objects:
for recipient in obj.get_final_recipients(message_type): for unique, recipient in get_final_recipients(obj).items():
if test_recipient: if test_recipient:
recipient = test_recipient unique = recipient = test_recipient
elif not recipient: if unique in recipients:
continue
if recipient in recipients:
if not test_recipient: if not test_recipient:
messages.info(request, messages.info(request,
_('A message for "{recipient}" has been ignored because ' _('A message for "{recipient}" has been ignored because '
'this one was duplicated in recipients list' 'this one was duplicated in recipients list'
).format(recipient=recipient)) ).format(recipient=recipient))
continue continue
recipients.append(recipient) recipients[unique] = recipient
send_messages = app_settings.MESSAGE_TYPES[message_type]['callable'] send_messages = app_settings.MESSAGE_TYPES[message_type]['send_messages']
send_messages(sender, subject, recipients, txt, html) send_messages(sender, subject, recipients, txt, html, extra_form_data)
if test_recipient: if test_recipient:
messages.info( messages.info(
...@@ -164,11 +162,12 @@ class SendMessageAdminMixin(object): ...@@ -164,11 +162,12 @@ class SendMessageAdminMixin(object):
msg = Message( msg = Message(
subject=subject, subject=subject,
post_date=datetime.now(), post_date=datetime.now(),
txt=txt, sent_txt=txt,
html=html, sent_html=html,
message=message,
author=request.user, author=request.user,
sender=sender, sender=sender,
recipients=', '.join(recipients), recipients=', '.join(recipients.values()),
test=bool(test_recipient), test=bool(test_recipient),
message_type=message_type, message_type=message_type,
) )
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from collections import OrderedDict
from importlib import import_module
import sys import sys
from django.utils.translation import ugettext_lazy as _
class AppSettings(object): class AppSettings(object):
...@@ -19,26 +15,37 @@ class AppSettings(object): ...@@ -19,26 +15,37 @@ class AppSettings(object):
if hasattr(self, '_MESSAGE_TYPES'): if hasattr(self, '_MESSAGE_TYPES'):
return self._MESSAGE_TYPES return self._MESSAGE_TYPES
message_types = getattr(self.settings, 'ADMIN_MESSAGE_MESSAGE_TYPES', { message_types = getattr(self.settings, 'ADMIN_MESSAGE_MESSAGE_TYPES', None)
'EMAIL': {'path_send_message': 'admin_message.adapters.email.send_message', if message_types is None:
'path_get_final_recipients_from_misc': ('admin_message.adapters.email' message_types = {
'.get_final_recipients_from_misc') 'EMAIL': {
'template': 'admin_message/email'} 'path_send_messages': 'admin_message.adapters.email.send_messages',
}) 'path_get_final_recipients': 'admin_message.adapters.email.get_final_recipients',
'path_get_senders': 'admin_message.adapters.email.get_senders',
'path_get_default_message': 'admin_message.adapters.email.get_default_message',
'label': _('Send an email'),
'template': 'admin_message/message_email',
}
}
if 'postman' in self.settings.INSTALLED_APPS:
message_types['POSTMAN'] = {
'path_send_messages': 'admin_message.adapters.postman.send_messages',
'path_get_final_recipients': 'admin_message.adapters.postman.get_final_recipients',
'path_get_senders': 'admin_message.adapters.postman.get_senders',
'path_get_default_message': 'admin_message.adapters.postman.get_default_message',
'label': _('Send private message'),
'template': 'admin_message/message_postman',
}
if not isinstance(message_types, OrderedDict): if not isinstance(message_types, OrderedDict):
message_types = OrderedDict(message_types) message_types = OrderedDict(message_types)
fnames = ('send_messages', 'get_final_recipients', 'get_senders', 'get_default_message')
for message_type, d in message_types.items(): for message_type, d in message_types.items():
module_path, func_name = d['path_send_message'].rsplit('.', 1) for fname in fnames:
func = getattr(import_module(module_path), func_name) module_path, func_name = d['path_' + fname].rsplit('.', 1)
if not callable(func): func = getattr(import_module(module_path), func_name)
raise Exception('"%s" from "%s" is not callable' % (module_path, func_name)) if not callable(func):
message_types[message_type]['send_message'] = func raise Exception('"%s" from "%s" is not callable' % (module_path, func_name))
message_types[message_type][fname] = func
module_path, func_name = d['path_get_final_recipients_from_misc'].rsplit('.', 1)
func = getattr(import_module(module_path), func_name)
if not callable(func):
raise Exception('"%s" from "%s" is not callable' % (module_path, func_name))
message_types[message_type]['path_get_final_recipients_from_misc'] = func
self._MESSAGE_TYPES = message_types self._MESSAGE_TYPES = message_types
return self._MESSAGE_TYPES return self._MESSAGE_TYPES
...@@ -49,35 +56,13 @@ class AppSettings(object): ...@@ -49,35 +56,13 @@ class AppSettings(object):
self._MESSAGE_TYPES_CHOICES = tuple((k, d['label']) for k, d in self.MESSAGE_TYPES.items()) self._MESSAGE_TYPES_CHOICES = tuple((k, d['label']) for k, d in self.MESSAGE_TYPES.items())
return self._MESSAGE_TYPES_CHOICES return self._MESSAGE_TYPES_CHOICES
def CALLABLES_PATHS(self):
if hasattr(self, '_CALLABLES_PATHS'):
return self._CALLABLES_PATHS
callables_paths = getattr(self.settings, 'ADMIN_MESSAGE_CALLABLES_PATHS', {})
if 'get_senders' not in callables_paths:
callables_paths['get_senders'] = 'admin_message.utils.get_senders'
if 'get_default_message' not in callables_paths:
callables_paths['get_default_message'] = 'admin_message.utils.get_default_message'
callables = {}
for k, path in callables_paths:
module_path, func_name = path.rsplit('.', 1)
func = getattr(import_module(module_path), func_name)
if not callable(func):
raise Exception('"%s" from "%s" is not callable' % (module_path, func_name))
callables[k] = func
self._CALLABLES_PATHS = callables_paths
self._CALLABLES = callables
return self._CALLABLES_PATHS
def CALLABLES(self):
if not hasattr(self, '_CALLABLES'):
self._CALLABLES_PATHS
return self._CALLABLES
# Ugly? Guido recommends this himself ... # Ugly? Guido recommends this himself ...
# http://mail.python.org/pipermail/python-ideas/2012-May/014969.html # http://mail.python.org/pipermail/python-ideas/2012-May/014969.html
app_settings = AppSettings() app_settings = AppSettings()
app_settings.__name__ = __name__ app_settings.__name__ = __name__
sys.modules[__name__] = app_settings sys.modules[__name__] = app_settings
from collections import OrderedDict # noqa
from importlib import import_module # noqa
from django.utils.translation import ugettext_lazy as _ # noqa
...@@ -19,21 +19,15 @@ class SendMessageForm(forms.Form): ...@@ -19,21 +19,15 @@ class SendMessageForm(forms.Form):
class ObjetsModelMultipleChoiceField(forms.ModelMultipleChoiceField): class ObjetsModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj): def label_from_instance(self, obj):
if len(app_settings.MESSAGE_TYPES_CHOICES) == 1: final_recipients = self.get_final_recipients(obj)
message_type = app_settings.MESSAGE_TYPES_CHOICES[0][0] nb = len(final_recipients)
get_final_recipients = app_settings.MESSAGE_TYPES[message_type]\
.get('get_final_recipients_from_misc')
final_recipients = get_final_recipients(obj)
nb = len(final_recipients)
else:
nb = None
if nb is None: if nb is None:
return _('%s') % obj return _('%s') % obj
elif not nb: elif not nb:
return _('%s : no valid recipient') % obj return _('%s : no valid recipient') % obj
elif nb == 1: elif nb == 1:
return '%s : %s' % (obj._meta.verbose_name, final_recipients[0]) return '%s : %s' % (obj._meta.verbose_name, final_recipients.items()[0])
else: else:
return _('%(type)s « %(name)s » : %(count)d recipients') % { return _('%(type)s « %(name)s » : %(count)d recipients') % {
'type': obj._meta.verbose_name, 'type': obj._meta.verbose_name,
...@@ -58,25 +52,25 @@ class SendMessageForm(forms.Form): ...@@ -58,25 +52,25 @@ class SendMessageForm(forms.Form):
subject = forms.CharField(label=_('Subject')) subject = forms.CharField(label=_('Subject'))
message = forms.CharField(label=_('Message'), widget=Textarea()) message = forms.CharField(label=_('Message'), widget=Textarea())
def __init__(self, queryset, user=None, *args, **kwargs): def __init__(self, queryset, user, message_type, *args, **kwargs):
if len(app_settings.MESSAGE_TYPES_CHOICES) == 1: self.message_type = message_type
message_type = app_settings.MESSAGE_TYPES_CHOICES[0][0] self.message_type_conf = app_settings.MESSAGE_TYPES[self.message_type]
else: senders = self.message_type_conf['get_senders'](user)
message_type = None
senders = app_settings.CALLABLES['get_senders'](user, message_type)
if 'initial' not in kwargs: if 'initial' not in kwargs:
kwargs['initial'] = {} kwargs['initial'] = {}
if 'message' not in kwargs['initial']: if 'message' not in kwargs['initial']:
kwargs['initial']['message'] = app_settings.CALLABLES['get_default_message'](user) kwargs['initial']['message'] = self.message_type_conf['get_default_message'](user)
if 'test_email' not in kwargs['initial'] and user.email in senders: if 'test_email' not in kwargs['initial'] and user.email in senders:
kwargs['initial']['test_email'] = user.email kwargs['initial']['test_email'] = user.email
if queryset and 'objects' not in kwargs['initial']: if queryset and 'objects' not in kwargs['initial']:
kwargs['initial']['objects'] = [obj for obj in queryset.all() kwargs['initial']['objects'] = [obj for obj in queryset.all()]
if obj.get_final_recipients_count(message_type)]
super(SendMessageForm, self).__init__(*args, **kwargs) super(SendMessageForm, self).__init__(*args, **kwargs)
if queryset: if queryset:
self.fields['objects'].get_final_recipients = self.message_type_conf\
.get('get_final_recipients')
self.fields['objects'].queryset = queryset self.fields['objects'].queryset = queryset
self.fields['sender'].choices = [(value, '%s <%s>' % (label, value)) self.fields['sender'].choices = [(value, '%s <%s>' % (label, value))
for value, label in senders.items()] for value, label in senders.items()]
...@@ -84,10 +78,9 @@ class SendMessageForm(forms.Form): ...@@ -84,10 +78,9 @@ class SendMessageForm(forms.Form):
def clean(self): def clean(self):
super(SendMessageForm, self).clean() super(SendMessageForm, self).clean()
objects = self.cleaned_data['objects'] objects = self.cleaned_data['objects']
message_type = self.cleaned_data['message_type'] get_final_recipients = self.message_type_conf['get_final_recipients']
get_final_recipients_from_misc = app_settings.CALLABLES['get_final_recipients_from_misc']
for obj in objects: for obj in objects:
recipients = get_final_recipients_from_misc(obj, message_type) recipients = get_final_recipients(obj)
if not recipients: if not recipients:
self.add_error('objects', _('Recipient "%s" has not any valid recipient' % obj)) self.add_error('objects', _('Recipient "%s" has not any valid recipient' % obj))
return self.cleaned_data return self.cleaned_data
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Message',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date de cr\xe9ation')),
('modification_date', models.DateTimeField(auto_now=True, verbose_name='modification date')),
('post_date', models.DateTimeField(null=True, verbose_name='posting date', blank=True)),
('test', models.BooleanField(default=False, verbose_name='is test')),
('message_type', models.CharField(max_length=14, verbose_name='is test')),
('sender', models.EmailField(max_length=254, verbose_name='exp\xe9diteur')),
('recipients', models.TextField(verbose_name='recipients')),
('subject', models.CharField(max_length=200, verbose_name='objet')),
('message', models.TextField(null=True, verbose_name='message', blank=True)),
('sent_txt', models.TextField(null=True, verbose_name='TXT content sent', blank=True)),
('sent_html', models.TextField(null=True, verbose_name='HTML content sent', blank=True)),
('author', models.ForeignKey(related_name='admin_message_message_related', verbose_name='auteur', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-post_date'],
'abstract': False,
'verbose_name': 'message',
'verbose_name_plural': 'messages',
},
),
]
...@@ -33,7 +33,7 @@ class MessageBase(models.Model): ...@@ -33,7 +33,7 @@ class MessageBase(models.Model):
message_type = models.CharField( message_type = models.CharField(
max_length=14, max_length=14,
verbose_name=_('is test'), verbose_name=_('is test'),
default=app_settings) null=False, blank=False)
sender = models.EmailField( sender = models.EmailField(
verbose_name=_('sender')) verbose_name=_('sender'))
recipients = models.TextField( recipients = models.TextField(
...@@ -65,17 +65,3 @@ class MessageBase(models.Model): ...@@ -65,17 +65,3 @@ class MessageBase(models.Model):
################################################################################################### ###################################################################################################
class Message(MessageBase): class Message(MessageBase):
pass pass