Skip to content

Commit

Permalink
Deuxième jet
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud-D committed Oct 12, 2020
1 parent 849094b commit 19cdcd7
Show file tree
Hide file tree
Showing 11 changed files with 413 additions and 2 deletions.
12 changes: 12 additions & 0 deletions fixtures/events_categories.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
- model: tutorialv2.EventCategory
pk: 1
fields:
name: beta
- model: tutorialv2.EventCategory
pk: 2
fields:
name: validation
- model: tutorialv2.EventCategory
pk: 3
fields:
name: author
71 changes: 71 additions & 0 deletions templates/tutorialv2/view/events.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{% extends "tutorialv2/base.html" %}
{% load profile %}
{% load thumbnail %}
{% load date %}
{% load i18n %}
{% load captureas %}


{% block title %}
{% blocktrans with title=content.title %}
Historique de "{{ title }}"
{% endblocktrans %}
{% endblock %}



{% block breadcrumb %}
<li><a href="{{ content.get_absolute_url }}">{{ content.title }}</a></li>
<li>{% trans "Historique des événements" %}</li>
{% endblock %}



{% block headline %}
{% if content.licence %}
<p class="license">
{{ content.licence }}
</p>
{% endif %}

<h1 {% if content.image %}class="illu"{% endif %}>
{% if content.image %}
<img src="{{ content.image.physical.tutorial_illu.url }}" alt="">
{% endif %}
{% blocktrans with title=content.title %}
Historique de "{{ title }}"
{% endblocktrans %}
</h1>

{% if content.description %}
<h2 class="subtitle">
{{ content.description }}
</h2>
{% endif %}

{% include 'tutorialv2/includes/tags_authors.part.html' with content=content online=False %}
{% endblock %}



{% block content %}
{% include "misc/paginator.html" with position="top" %}

<table class="fullwidth commits-list">
<thead>
<tr>
<th width="20%">{% trans "Date" %}</th>
<th>{% trans "Description" %}</th>
</tr>
</thead>
<tbody>
{% for e in events %}
<tr>
<td>{{ e.date | format_date:True }}</td>
<td>{{ e.description | safe }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "misc/paginator.html" with position="bottom" %}
{% endblock %}
3 changes: 3 additions & 0 deletions zds/tutorialv2/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from zds.tutorialv2.models.database import PublishableContent, Validation, ContentReaction, PublishedContent, \
PickListOperation, ContentRead, PublicationEvent, ContentContributionRole
from zds.tutorialv2.models.events import Event, EventCategory


class PublishableContentAdmin(admin.ModelAdmin):
Expand Down Expand Up @@ -77,3 +78,5 @@ class ContentReviewTypeAdmin(admin.ModelAdmin):
admin.site.register(ContentRead, ContentReadAdmin)
admin.site.register(PublicationEvent, PublicationEventAdmin)
admin.site.register(ContentContributionRole, ContentReviewTypeAdmin)
admin.site.register(Event)
admin.site.register(EventCategory)
42 changes: 42 additions & 0 deletions zds/tutorialv2/migrations/0032_event_eventcategory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Generated by Django 2.2.16 on 2020-10-08 20:02

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('tutorialv2', '0031_source_is_url'),
]

operations = [
migrations.CreateModel(
name='EventCategory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True)),
],
options={
'verbose_name': "Catégorie d'événement",
'verbose_name_plural': "Catégories d'événement",
},
),
migrations.CreateModel(
name='Event',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateTimeField(auto_now_add=True)),
('type', models.CharField(max_length=100)),
('actor', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
('author', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='event_author', to=settings.AUTH_USER_MODEL)),
('content', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tutorialv2.PublishableContent')),
],
options={
'verbose_name': 'Événement sur un contenu',
'verbose_name_plural': 'Événements sur un contenu',
},
),
]
195 changes: 195 additions & 0 deletions zds/tutorialv2/models/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
from django.db import models
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _

from zds.tutorialv2.models.database import PublishableContent
from zds.tutorialv2 import signals
from zds.tutorialv2.views.authors import AddAuthorToContent, RemoveAuthorFromContent
from zds.tutorialv2.views.beta import ManageBetaContent
from zds.tutorialv2.views.validations_contents import (ReserveValidation, AskValidationForContent, CancelValidation,
RejectValidation, AcceptValidation, RevokeValidation)

# General architecture
# - Event are assigned to categories (e.g. for filtering purposes)
# - Events have a type
# - Signals map to types
# - In turns, types are stored in database and used for displaying the event

# Notes on addition/deletion/update of managed signals
# - addition:
# 1. Add a signal->type mapping,
# 2. Write the corresponding descriptor,
# 3. Map the type to the descriptor and voilà !
# - deletion
# 1. Remove the signal->type mapping and the correspondong receiver
# This will make it impossible to record new events coming from this signal.
# 2. Do nothing more.
# Events in database should be displayed properly, so keep the display logic (e.g. descriptor).
# - update
# 1. If a type name was to be updated for some reason, the database should also be updated (update all records)
# to match this change.


# Signals known to the module
signal_to_types = {
signals.author_added: 'author_added',
signals.author_removed: 'author_removed',
signals.beta_activated: 'beta_activated',
signals.beta_deactivated: 'beta_deactivated',
signals.validation_requested: 'validation_requested',
signals.validation_canceled: 'validation_canceled',
signals.validation_accepted: 'validation_accepted',
signals.validation_rejected: 'validation_rejected',
signals.validation_revoked: 'validation_revoked',
signals.validation_reserved: 'validation_reserved',
signals.validation_unreserved: 'validation_unreserved',
}


class EventCategory(models.Model):
class Meta:
verbose_name = "Catégorie d'événement"
verbose_name_plural = "Catégories d'événement"

name = models.CharField(unique=True, max_length=100)


class Event(models.Model):
class Meta:
verbose_name = 'Événement sur un contenu'
verbose_name_plural = 'Événements sur un contenu'

# Base fields
date = models.DateTimeField(auto_now_add=True)
content = models.ForeignKey(PublishableContent, on_delete=models.CASCADE)
actor = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
type = models.CharField(max_length=100)

# Field used by author events
author = models.ForeignKey(User, related_name='event_author', on_delete=models.SET_NULL, null=True)

# Content version
# TODO version of the content concerned by the event

@property
def description(self):
try:
return descriptors[self.type.__str__()](self)
except KeyError:
return describe_generic(self)


# Event descriptors

def describe_generic(event):
return _('{} a déclenché un événement inconnu.').format(event.actor)


def describe_author_added(event):
return _('<a href="{}">{}</a> a ajouté <a href="{}">{}</a> à la liste des auteurs.').format(
reverse('member-detail', args=[event.actor.username]), event.actor,
reverse('member-detail', args=[event.author.username]), event.author)


def describe_author_removed(event):
return _('<a href="{}">{}</a> a supprimé <a href="{}">{}</a> de la liste des auteurs.').format(
reverse('member-detail', args=[event.actor.username]), event.actor,
reverse('member-detail', args=[event.author.username]), event.author)


def describe_beta_deactivated(event):
return _('<a href="{}">{}</a> a désactivé la bêta.').format(
reverse('member-detail', args=[event.actor.username]), event.actor)


def describe_beta_activated(event):
return _('<a href="{}">{}</a> a activé la bêta.').format(
reverse('member-detail', args=[event.actor.username]), event.actor)


def describe_beta_updated(event):
return _('<a href="{}">{}</a> a mis à jour la bêta.').format(
reverse('member-detail', args=[event.actor.username]), event.actor)


def describe_validation_reserved(event):
return _('<a href="{}">{}</a> a réservé le contenu pour validation.').format(
reverse('member-detail', args=[event.actor.username]), event.actor)


def describe_validation_unreserved(event):
return _('<a href="{}">{}</a> a annulé la réservation du contenu pour validation.').format(
reverse('member-detail', args=[event.actor.username]), event.actor)


def describe_validation_requested(event):
return _('<a href="{}">{}</a> a demandé la validation du contenu.').format(
reverse('member-detail', args=[event.actor.username]), event.actor)


def describe_validation_canceled(event):
return _('<a href="{}">{}</a> a annulé la demande de validation du contenu.').format(
reverse('member-detail', args=[event.actor.username]), event.actor)


def describe_validation_accepted(event):
return _('<a href="{}">{}</a> a accepté le contenu pour publication.').format(
reverse('member-detail', args=[event.actor.username]), event.actor)


def describe_validation_rejected(event):
return _('<a href="{}">{}</a> a refusé le contenu pour publication.').format(
reverse('member-detail', args=[event.actor.username]), event.actor)


def describe_validation_revoked(event):
return _('<a href="{}">{}</a> a dépublié le contenu.').format(
reverse('member-detail', args=[event.actor.username]), event.actor)


descriptors = {
'author_added': describe_author_added,
'author_removed': describe_author_removed,
'beta_activated': describe_beta_activated,
'beta_deactivated': describe_beta_deactivated,
'validation_requested': describe_validation_requested,
'validation_canceled': describe_validation_canceled,
'validation_accepted': describe_validation_accepted,
'validation_rejected': describe_validation_rejected,
'validation_revoked': describe_validation_revoked,
'validation_reserved': describe_validation_reserved,
'validation_unreserved': describe_validation_unreserved,
}


@receiver(signals.beta_activated, sender=ManageBetaContent)
@receiver(signals.beta_deactivated, sender=ManageBetaContent)
def record_event_beta_management(sender, **kwargs):
print(kwargs)
Event(content=kwargs['content'],
actor=kwargs['actor'],
type=signal_to_types[kwargs['signal']]).save()


@receiver(signals.author_added, sender=AddAuthorToContent)
@receiver(signals.author_removed, sender=RemoveAuthorFromContent)
def record_event_author_management(sender, *_, **kwargs):
Event(content=kwargs['content'],
actor=kwargs['actor'],
author=kwargs['author'],
type=signal_to_types[kwargs['signal']]).save()


@receiver(signals.validation_requested, sender=AskValidationForContent)
@receiver(signals.validation_canceled, sender=CancelValidation)
@receiver(signals.validation_accepted, sender=AcceptValidation)
@receiver(signals.validation_rejected, sender=RejectValidation)
@receiver(signals.validation_revoked, sender=RevokeValidation)
@receiver(signals.validation_reserved, sender=ReserveValidation)
@receiver(signals.validation_unreserved, sender=ReserveValidation)
def record_event_validation_management(sender, **kwargs):
Event(content=kwargs['content'],
actor=kwargs['actor'],
type=signal_to_types[kwargs['signal']]).save()
17 changes: 17 additions & 0 deletions zds/tutorialv2/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,20 @@

# is sent whenever a content is unpublished
content_unpublished = Signal(providing_args=['instance', 'target', 'moderator'])

# Beta management
beta_activated = Signal(providing_args=['content', 'actor'])
beta_deactivated = Signal(providing_args=['content', 'actor'])

# Author management
author_added = Signal(providing_args=['content', 'actor', 'author'])
author_removed = Signal(providing_args=['content', 'actor', 'author'])

# Validation management
validation_requested = Signal(providing_args=['content', 'actor'])
validation_canceled = Signal(providing_args=['content', 'actor'])
validation_accepted = Signal(providing_args=['content', 'actor'])
validation_rejected = Signal(providing_args=['content', 'actor'])
validation_revoked = Signal(providing_args=['content', 'actor'])
validation_reserved = Signal(providing_args=['content', 'actor'])
validation_unreserved = Signal(providing_args=['content', 'actor'])
4 changes: 4 additions & 0 deletions zds/tutorialv2/urls/urls_contents.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from zds.tutorialv2.views.contents import (DisplayContent, CreateContent, EditContent, EditContentLicense,
DeleteContent)
from zds.tutorialv2.views.events import EventList
from zds.tutorialv2.views.validations_contents import ActivateJSFiddleInContent
from zds.tutorialv2.views.containers_extracts import CreateContainer, DisplayContainer, EditContainer, \
CreateExtract, EditExtract, DeleteContainerOrExtract, MoveChild
Expand Down Expand Up @@ -212,4 +213,7 @@

re_path(r'^$', RedirectView.as_view(
pattern_name='publication:list', permanent=True), name='list'),

# List of events
re_path(r'^events/(?P<pk>\d+)/$', EventList.as_view(), name='events'),
]
Loading

0 comments on commit 19cdcd7

Please sign in to comment.