PK O] snitch/__init__.py"""Django app made to integrate generic events that create notifications that
can be sent to users using several backends.
By default, it integrates push notifications and email to send the
notifications.
"""
from django.utils.module_loading import autodiscover_modules
from snitch.decorators import register, dispatch
from snitch.handlers import manager, EventHandler
from snitch.helpers import explicit_dispatch, get_notification_model
__all__ = [
"register",
"manager",
"EventHandler",
"dispatch",
"explicit_dispatch",
"get_notification_model",
]
__version__ = "1.0"
def autodiscover():
autodiscover_modules("events", register_to=manager)
default_app_config = "snitch.apps.SnitchConfig"
PK OML L snitch/admin.pyfrom django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from snitch.models import EventType, Event, Notification
def notify_action(modeladmin, request, queryset):
"""Explicit creates notifications for events."""
for event in queryset:
event.notify()
modeladmin.message_user(request, _("Events notified!"))
notify_action.short_description = _("Notify events")
def send_action(modeladmin, request, queryset):
"""Explicit sends the notifications using the backend."""
for notification in queryset:
notification.send(send_async=True)
modeladmin.message_user(request, _("Notifications sent!"))
send_action.short_description = _("Send notifications")
@admin.register(EventType)
class EventTypeAdmin(admin.ModelAdmin):
list_display = ["id", "verb", "enabled"]
@admin.register(Event)
class EventAdmin(admin.ModelAdmin):
list_display = ["id", "actor", "verb", "trigger", "target", "notified", "created"]
list_filter = ["verb", "notified"]
actions = [notify_action]
@admin.register(Notification)
class NotificationAdmin(admin.ModelAdmin):
list_display = ["id", "event", "user", "read", "received", "created"]
list_filter = ["event__verb", "read", "sent"]
search_fields = ["user__email"]
actions = [send_action]
autocomplete_fields = ["user"]
PK
O{ snitch/apps.pyfrom django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class SimpleSnitchConfig(AppConfig):
"""Simple AppConfig which does not do automatic discovery."""
name = "snitch"
verbose_name = _("Snitch")
class SnitchConfig(SimpleSnitchConfig):
"""The default AppConfig for admin which does automatic discovery."""
def ready(self):
super().ready()
self.module.autodiscover()
PK O>^ snitch/backends.pyimport logging
from django.contrib.auth import get_user_model
from push_notifications.gcm import GCMError
from push_notifications.models import GCMDevice, APNSDevice
from snitch.emails import TemplateEmailMessage
from snitch.settings import ENABLED_SEND_NOTIFICATIONS
logger = logging.getLogger(__name__)
User = get_user_model()
class AbstractBackend:
"""Abstract backend class for notifications."""
def __init__(self, notification):
self.notification = notification
self.title = self.notification.event.title()
self.text = self.notification.event.text()
self.action = self.notification.event.action()
def send(self):
"""A subclass should to implement the send method."""
raise NotImplementedError
class PushNotificationBackend(AbstractBackend):
"""A backend class to send push notifications depending on the platform."""
def _send_android(self):
devices = GCMDevice.objects.filter(user=self.notification.user)
message = self.text
extra = {}
if self.title:
extra["title"] = self.title
if self.action:
extra["click_action"] = self.action
try:
devices.send_message(message=message, extra=extra)
except GCMError:
logger.warning("Error sending push message")
def _send_ios(self):
devices = APNSDevice.objects.filter(user=self.notification.user)
message = self.text
extra = {}
if self.title:
message = {"title": self.title, "body": self.text}
if self.action:
extra["click_action"] = self.action
try:
devices.send_message(message=message, extra=extra)
except GCMError:
logger.warning("Error sending push message")
def send(self):
"""Send message for each platform."""
if ENABLED_SEND_NOTIFICATIONS:
self._send_android()
self._send_ios()
class EmailNotificationBackend(AbstractBackend):
"""Backend for using the email app to send emails."""
def send(self):
"""Sends the email."""
if ENABLED_SEND_NOTIFICATIONS:
# Gets the handler to extract the arguments from template_email_kwargs
handler = self.notification.event.handler()
if hasattr(handler, "template_email_kwargs"):
kwargs = handler.template_email_kwargs
# Gets to email
email = (
getattr(User, "EMAIL_FIELD")
if hasattr(User, "EMAIL_FIELD")
else None
)
if email:
kwargs.update(
{"to": getattr(self.notification.user, "EMAIL_FIELD")}
)
# Context
context = kwargs.get("context", {})
context.update({"notification": self.notification})
kwargs.update({"context": context})
# Sends email
email = TemplateEmailMessage(**kwargs)
email.send(
use_async=handler.template_email_async
if hasattr(handler, "template_email_async")
else True
)
PK *jO
snitch/decorators.pyfrom snitch.helpers import extract_actor_trigger_target
def register(verb, verbose=None):
"""Decorator to register an event with its handler.
@events.register("verb", _("verb verbose"))
class Handler(events.EventHandler):
pass
"""
from snitch.handlers import manager
def _event_handler_wrapper(event_handler_class):
manager.register(verb, event_handler_class, verbose=verbose)
return event_handler_class
return _event_handler_wrapper
def dispatch(verb, method=False, config=None):
"""Decorator to dispatch an event when a method or function is called.
The arguments attribute if to configure how to extract the actor, trigger and
target from the decorated function arguments.
config = {
"args": ("actor", "trigger", "target") # The position determines de type
"kwargs": {"actor": "", "trigger": "", "target": ""}
}
Example:
@events.dispatch("verb")
def method(self, trigger, target=None):
# self is the actor
pass
"""
from django.contrib.contenttypes.models import ContentType
from snitch.models import Event, EventType
from snitch.handlers import manager
def _decorator(func):
"""Decorator itself."""
def _wrapper_trigger_action(*args, **kwargs):
"""Wrapped function with the decorator."""
# Calls the function and saves the result
result = func(*args, **kwargs)
# Check if verb is enabled or not
if EventType.objects.filter(verb=verb, enabled=False).exists():
return result
# Extract actor, trigger and target
# If it isn't specified in arguments attribute, use the handler
if config is None:
handler_class = manager.handler_class(verb)
actor, trigger, target = handler_class.extract_actor_trigger_target(
method, *args, **kwargs
)
# If it's explicit, use the arguments attribute
else:
if not isinstance(config, dict) or (
"args" not in config and "kwargs" not in config
):
return result
actor, trigger, target = extract_actor_trigger_target(
config, args, kwargs
)
# Creates the event if there is an actor
if actor:
event = Event(
actor_content_type=ContentType.objects.get_for_model(actor),
actor_object_id=actor.pk,
verb=verb,
)
if trigger and hasattr(trigger, "pk") and trigger.pk is not None:
try:
event.trigger_content_type = ContentType.objects.get_for_model(
trigger
)
event.trigger_object_id = trigger.pk
except ContentType.DoesNotExist:
pass
if target and hasattr(target, "pk") and target.pk is not None:
try:
event.target_content_type = ContentType.objects.get_for_model(
target
)
event.target_object_id = target.pk
except ContentType.DoesNotExist:
pass
event.save()
return result
return _wrapper_trigger_action
return _decorator
PK
lO{4jC snitch/emails.pyimport warnings
import bleach
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from snitch.settings import ENABLED_SEND_NOTIFICATIONS
from snitch.tasks import send_email_asynchronously
class TemplateEmailMessage(object):
"""An object to handle emails based on templates, with automatic plain
alternatives.
"""
default_template_name = ""
default_subject = ""
default_from_email = ""
fake = False
def __init__(
self,
to,
subject=None,
context=None,
from_email=None,
attaches=None,
template_name=None,
):
self.template_name = (
self.default_template_name if template_name is None else template_name
)
if not self.template_name:
warnings.warn("You have to specify the template name")
if not isinstance(to, list) and not isinstance(to, tuple):
self.to = [to]
self.subject = "%s" % self.default_subject if subject is None else subject
self.from_email = self.default_from_email if from_email is None else from_email
self.attaches = [] if attaches is None else attaches
self.default_context = {} if context is None else context
def get_context(self):
"""Hook to customize context."""
# Add default context
current_site = Site.objects.get_current()
self.default_context.update({"site": current_site})
return self.default_context
def preview(self):
"""Renders the message for a preview."""
context = self.get_context()
message = render_to_string(self.template_name, context, using="django")
return message
def async_send(self, message, message_txt):
if not self.fake:
send_email_asynchronously.delay(
self.subject, message_txt, message, self.from_email, self.to
)
if self.attaches:
warnings.warn(
"Attaches will not added to the email, use async=False to send "
"attaches."
)
def sync_send(self, message, message_txt):
if not self.fake:
email = EmailMultiAlternatives(
subject=self.subject,
body=message_txt,
from_email=self.from_email,
to=self.to,
)
email.attach_alternative(message, "text/html")
for attach in self.attaches:
attach_file_name, attach_content, attach_content_type = attach
email.attach(attach_file_name, attach_content, attach_content_type)
email.send()
def send(self, use_async=True):
"""Sends the email at the moment or using a Celery task."""
if not ENABLED_SEND_NOTIFICATIONS:
return
context = self.get_context()
message = render_to_string(self.template_name, context, using="django")
message_txt = message.replace("\n", "")
message_txt = message_txt.replace("
", "\n")
message_txt = message_txt.replace("", "\n\n")
message_txt = bleach.clean(message_txt, strip=True)
if use_async:
self.async_send(message, message_txt)
else:
self.sync_send(message, message_txt)
class AdminsTemplateEmailMessage(TemplateEmailMessage):
"""Emails only for admins."""
def __init__(self, subject=None, context=None, from_email=None):
to = [a[1] for a in settings.ADMINS]
super().__init__(to, subject=subject, context=context, from_email=from_email)
class ManagersTemplateEmailMessage(TemplateEmailMessage):
"""Emails only for mangers."""
def __init__(self, subject=None, context=None, from_email=None):
to = [m[1] for m in settings.MANAGERS]
super().__init__(to, subject=subject, context=context, from_email=from_email)
PK
O]OѶ snitch/exceptions.pyclass HandlerError(Exception):
"""An error configuring the event handler."""
pass
class AlreadyRegistered(Exception):
"""Event handlers already register."""
pass
PK
OX
snitch/handlers.pyfrom django.contrib.auth import get_user_model
from django.utils.translation import ugettext_lazy as _
from snitch.exceptions import HandlerError
from snitch.helpers import extract_actor_trigger_target, get_notification_model
class EventHandler:
"""Base event backend to generic even types."""
should_notify = True
should_send = True
dispatch_config = {"args": ("actor", "trigger", "target")}
action = None
title = None
text = None
notification_backends = []
@classmethod
def extract_actor_trigger_target(cls, method, *args, **kwargs):
"""Extracts actor, trigger and target from the args and kwargs
given as parameters. Override to implement a specific extractor.
"""
if not isinstance(cls.dispatch_config, dict) or (
"args" not in cls.dispatch_config and "kwargs" not in cls.dispatch_config
):
raise HandlerError(_("The dispatch config is incorrect."))
return extract_actor_trigger_target(
config=cls.dispatch_config, args=args, kwargs=kwargs
)
def __init__(self, event):
self.event = event
def _default_dynamic_text(self):
"""Makes an event human readable."""
text = "{} {}".format(str(self.event.actor), self.event.verb)
if self.event.trigger:
text = "{} {}".format(text, str(self.event.trigger))
if self.event.target:
text = "{} {}".format(text, str(self.event.target))
return text
def get_text(self):
"""Override to handle different human readable implementations."""
return self.text or self._default_dynamic_text()
def get_title(self):
"""Gets the title for the event. To be hooked."""
return self.title
def get_action(self):
"""Gets the action depending on the verb. To be hooked."""
return self.action
def audience(self):
"""Gets the audience of the event. None by default, to be hooked by the user."""
User = get_user_model()
return User.objects.none()
def notify(self):
"""Creates a notification fot each user in the audience."""
Notification = get_notification_model()
for user in self.audience():
notification = Notification(event=self.event, user=user)
notification.save()
class EventManager:
"""The event manager in the responsible of handling the registration of the
handlers with the verbs.
"""
def __init__(self):
self._registry = {}
self._verbs = {}
def register(self, verb, handler, verbose=None):
"""Register a handler with a verb, and the verbose form of the verb."""
if not issubclass(handler, EventHandler):
raise HandlerError(
_(f"The handler {handler} have to inherit from EventHandler.")
)
self._verbs[verb] = verbose if verbose else verb
self._registry[verb] = handler
def choices(self):
"""Gets a tuple of tuples with the registers verbs and its verbose form, to be
used as choices."""
return tuple(self._verbs.items())
def handler_class(self, verb):
"""Returns a class instance of the handler for the given verb."""
return self._registry.get(verb)
def handler(self, event):
"""Returns an instance of the handler for the given event."""
return self.handler_class(event.verb)(event)
# This global object represents the singleton event manager object
manager = EventManager()
PK kO۔O O snitch/helpers.pyfrom django.apps import apps as django_apps
from django.core.exceptions import ImproperlyConfigured
from snitch.settings import NOTIFICATION_MODEL
def get_notification_model():
"""Return the Notification model that is active in this project."""
try:
return django_apps.get_model(NOTIFICATION_MODEL, require_ready=False)
except ValueError:
raise ImproperlyConfigured(
"NOTIFICATION_MODEL must be of the form 'app_label.model_name'"
)
except LookupError:
raise ImproperlyConfigured(
"NOTIFICATION_MODEL refers to model '%s' that has not been installed"
% NOTIFICATION_MODEL
)
def explicit_dispatch(verb, config=None, *args, **kwargs):
"""Helper to explicit dispatch an event without using a decorator."""
from snitch.decorators import dispatch
return dispatch(verb, config)(lambda *args, **kwargs: None)(*args, **kwargs)
def extract_actor_trigger_target(config, args, kwargs):
"""Extracts the actor, trigger and target using the arguments config given from
the generic arguments args and kwargs.
"""
actor = trigger = target = None
if "args" in config:
arguments_args = config.get("args", tuple())
try:
actor = args[arguments_args.index("actor")]
except (ValueError, IndexError):
pass
try:
trigger = args[arguments_args.index("trigger")]
except (ValueError, IndexError):
pass
try:
target = args[arguments_args.index("target")]
except (ValueError, IndexError):
pass
if "kwargs" in config:
arguments_kwargs = config.get("kwargs", dict())
try:
actor = kwargs[arguments_kwargs.get("actor")]
except (ValueError, KeyError):
pass
try:
trigger = kwargs[arguments_kwargs.get("trigger")]
except (ValueError, KeyError):
pass
try:
target = kwargs[arguments_kwargs.get("target")]
except (ValueError, KeyError):
pass
return actor, trigger, target
PK O=> > snitch/models.pyfrom django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils.translation import ugettext_lazy as _
from model_utils.models import TimeStampedModel
from snitch.handlers import manager
User = get_user_model()
class EventType(models.Model):
"""Explicit model for Event types, represented by the event verb. It's used to
enable or disable the generation of notifications.
"""
verb = models.CharField(
max_length=255, null=True, choices=manager.choices(), unique=True
)
enabled = models.BooleanField(default=True, verbose_name=_("enabled"))
class Meta:
verbose_name = _("event type")
verbose_name_plural = _("event types")
ordering = ("verb",)
def __str__(self):
return str(self.verb)
class Event(TimeStampedModel):
"""A 'event' is generated when an 'actor' performs 'verb', involving 'action',
in the 'target'.
It could be:
Reference: http://activitystrea.ms/specs/atom/1.0/
"""
actor_content_type = models.ForeignKey(
ContentType,
related_name="actor_actions",
null=True,
on_delete=models.CASCADE,
verbose_name=_("actor content type"),
)
actor_object_id = models.PositiveIntegerField(_("actor object id"), null=True)
actor = GenericForeignKey("actor_content_type", "actor_object_id")
verb = models.CharField(
_("verb"), max_length=255, null=True, choices=manager.choices()
)
trigger_content_type = models.ForeignKey(
ContentType,
related_name="trigger_actions",
blank=True,
null=True,
on_delete=models.CASCADE,
verbose_name=_("trigger content type"),
)
trigger_object_id = models.PositiveIntegerField(
_("trigger object id"), blank=True, null=True
)
trigger = GenericForeignKey("trigger_content_type", "trigger_object_id")
target_content_type = models.ForeignKey(
ContentType,
related_name="target_actions",
blank=True,
null=True,
on_delete=models.CASCADE,
verbose_name=_("target content type"),
)
target_object_id = models.PositiveIntegerField(
_("target object id"), blank=True, null=True
)
target = GenericForeignKey("target_content_type", "target_object_id")
notified = models.BooleanField(_("notified"), default=False)
class Meta:
verbose_name = _("event")
verbose_name_plural = _("events")
def __str__(self):
return self.text()
def handler(self):
"""Gets the handler for the event. Save the instance of the handler in the
model.
"""
if not hasattr(self, "_handler_instance"):
self._handler_instance = manager.handler(self)
return self._handler_instance
def text(self):
"""Gets the human readable text for the event."""
handler = self.handler()
return handler.get_text()
def title(self):
"""Gets the title for the event."""
handler = self.handler()
return handler.get_title()
def action(self):
"""Gets the action depending on the verb."""
handler = self.handler()
return handler.get_action()
def notify(self):
"""Creates the notifications associated to this action, ."""
handler = self.handler()
if handler.should_notify:
handler.notify()
self.notified = True
self.save()
def save(self, *args, **kwargs):
result = super().save(*args, **kwargs)
if not self.notified:
self.notify()
return result
class AbstractNotification(TimeStampedModel):
"""A notification is sent to an user, and it's always related with an event."""
event = models.ForeignKey(
Event,
verbose_name=_("event"),
related_name="notifications",
on_delete=models.CASCADE,
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name="notifications", on_delete=models.CASCADE
)
sent = models.BooleanField(_("sent"), default=False)
received = models.BooleanField(_("received"), default=False)
read = models.BooleanField(_("read"), default=False)
class Meta:
verbose_name = _("notification")
verbose_name_plural = _("notifications")
ordering = ("-created",)
abstract = True
def __str__(self):
return "'{}' to {}".format(str(self.event), str(self.user))
def send(self, send_async=False):
"""Sends a push notification to the devices of the user."""
from .tasks import push_task
handler = self.event.handler()
if handler.should_send:
if send_async:
push_task.delay(self.pk)
else:
for backend_class in handler.notification_backends:
backend = backend_class(self)
backend.send()
self.sent = True
self.save()
def save(self, *args, **kwargs):
"""Overwrite to sending push notifications when saving."""
is_insert = self._state.adding
super().save(*args, **kwargs)
if is_insert:
self.send()
class Notification(AbstractNotification):
"""Initial notification model that can be swappable."""
class Meta(AbstractNotification.Meta):
swappable = "SNITCH_NOTIFICATION_MODEL"
PK O@iF41 1 snitch/settings.pyfrom django.conf import settings
# Needed to build and publish with Flit
# ------------------------------------------------------------------------------
SECRET_KEY = "snitch"
# Specific project configuration
# ------------------------------------------------------------------------------
ENABLED_SEND_NOTIFICATIONS = getattr(
settings, "SNITCH_ENABLED_SEND_NOTIFICATIONS", True
)
ENABLED_SEND_EMAILS = getattr(settings, "SNITCH_ENABLED_SEND_EMAILS", True)
NOTIFICATION_MODEL = getattr(
settings, "SNITCH_NOTIFICATION_MODEL", "snitch.Notification"
)
PK
Oig)O O snitch/tasks.pyfrom celery.task import task
from django.core.mail import EmailMultiAlternatives
@task(serializer="json")
def push_task(notification_pk):
"""A Celery task to send push notifications related with a given Notification
model."""
from snitch.events import get_notification_model
Notification = get_notification_model()
try:
notification = Notification.objects.get(pk=notification_pk)
except Notification.DoesNotExist:
return False
notification.push(send_async=False)
@task(serializer="json")
def send_email_asynchronously(subject, message_txt, message, from_email, to):
"""Sends an email as a asynchronous task."""
email = EmailMultiAlternatives(
subject=subject, body=message_txt, from_email=from_email, to=to
)
email.attach_alternative(message, "text/html")
email.send()
PK 1O&hq ) snitch/locale/es_ES/LC_MESSAGES/django.po# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-08-14 16:55+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: snitch/admin.py:11
msgid "Events notified!"
msgstr "¡Eventos notificados!"
#: snitch/admin.py:14
msgid "Notify events"
msgstr "Notificar eventos"
#: snitch/admin.py:21
msgid "Notifications sent!"
msgstr "¡Notificaciones enviadas!"
#: snitch/admin.py:24
msgid "Send notifications"
msgstr "Enviar notificaciones"
#: snitch/apps.py:9
msgid "Snitch"
msgstr "Snitch"
#: snitch/handlers.py:27
msgid "The dispatch config is incorrect."
msgstr "La configuración de envío es incorrecta."
#: snitch/handlers.py:82
#, python-brace-format
msgid "The handler {handler} have to inherit from EventHandler."
msgstr "El manejador {handler} ha de heredar de EventHandler."
#: snitch/models.py:22
msgid "enabled"
msgstr "habilitado"
#: snitch/models.py:25
msgid "event type"
msgstr "tipo de evento"
#: snitch/models.py:26
msgid "event types"
msgstr "tipos de evento"
#: snitch/models.py:50
msgid "actor content type"
msgstr "tipo de actor"
#: snitch/models.py:52
msgid "actor object id"
msgstr "ID de actor"
#: snitch/models.py:56
msgid "verb"
msgstr "verbo"
#: snitch/models.py:65
msgid "trigger content type"
msgstr "tipo de disparador"
#: snitch/models.py:68
msgid "trigger object id"
msgstr "ID de disparador"
#: snitch/models.py:78
msgid "target content type"
msgstr "tipo de objetivo"
#: snitch/models.py:81
msgid "target object id"
msgstr "ID de objetivo"
#: snitch/models.py:85
msgid "notified"
msgstr "notificado"
#: snitch/models.py:88 snitch/models.py:137
msgid "event"
msgstr "evento"
#: snitch/models.py:89
msgid "events"
msgstr "eventos"
#: snitch/models.py:144
msgid "sent"
msgstr "enviado"
#: snitch/models.py:145
msgid "received"
msgstr "recibido"
#: snitch/models.py:146
msgid "read"
msgstr "leído"
#: snitch/models.py:149
msgid "notification"
msgstr "notificación"
#: snitch/models.py:150
msgid "notifications"
msgstr "notificaciones"
PK OZ* ! snitch/migrations/0001_initial.py# Generated by Django 2.2.4 on 2019-08-14 14:55
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import model_utils.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("contenttypes", "0002_remove_content_type_name"),
]
operations = [
migrations.CreateModel(
name="EventType",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("verb", models.CharField(max_length=255, null=True, unique=True)),
("enabled", models.BooleanField(default=True, verbose_name="enabled")),
],
options={
"verbose_name": "event type",
"verbose_name_plural": "event types",
"ordering": ("verb",),
},
),
migrations.CreateModel(
name="Event",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
(
"actor_object_id",
models.PositiveIntegerField(
null=True, verbose_name="actor object id"
),
),
(
"verb",
models.CharField(max_length=255, null=True, verbose_name="verb"),
),
(
"trigger_object_id",
models.PositiveIntegerField(
blank=True, null=True, verbose_name="trigger object id"
),
),
(
"target_object_id",
models.PositiveIntegerField(
blank=True, null=True, verbose_name="target object id"
),
),
(
"notified",
models.BooleanField(default=False, verbose_name="notified"),
),
(
"actor_content_type",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="actor_actions",
to="contenttypes.ContentType",
verbose_name="actor content type",
),
),
(
"target_content_type",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="target_actions",
to="contenttypes.ContentType",
verbose_name="target content type",
),
),
(
"trigger_content_type",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="trigger_actions",
to="contenttypes.ContentType",
verbose_name="trigger content type",
),
),
],
options={"verbose_name": "event", "verbose_name_plural": "events"},
),
migrations.CreateModel(
name="Notification",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
("sent", models.BooleanField(default=False, verbose_name="sent")),
(
"received",
models.BooleanField(default=False, verbose_name="received"),
),
("read", models.BooleanField(default=False, verbose_name="read")),
(
"event",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="notifications",
to="snitch.Event",
verbose_name="event",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="notifications",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "notification",
"verbose_name_plural": "notifications",
"ordering": ("-created",),
"abstract": False,
"swappable": "SNITCH_NOTIFICATION_MODEL",
},
),
]
PK O snitch/migrations/__init__.pyPK m
Oua8 8 # django_snitch-1.0.dist-info/LICENSEThe MIT License (MIT)
Copyright (c) 2019 Marcos Gabarda
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.PK !HP O ! django_snitch-1.0.dist-info/WHEELHM
K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&Ur PK !Ht $ django_snitch-1.0.dist-info/METADATAW[6~PCXM7m6LvH2NdY
J2ȲͲ$-ɢs;sq5vRC̻t8C|+\ř&S+\z9,İA8MQ@kXSJFSֈHjD\h25H- GZj8l2#" j},