Skip to content

Commit

Permalink
WIP Partage par lien
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud-D committed Nov 10, 2024
1 parent a6ae90d commit 0c025d4
Show file tree
Hide file tree
Showing 17 changed files with 905 additions and 2 deletions.
106 changes: 106 additions & 0 deletions templates/tutorialv2/view/list_shareable_links.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
{% extends "tutorialv2/base.html" %}
{% load i18n %}

{% block content %}

<h1>{% blocktrans %} Liens de partage pour « {{ content }} » {% endblocktrans %}</h1>

<p>{% trans "Diffusez votre contenu en partageant un simple lien accessible sans incription sur le site." %}</p>

<p>{% trans "Les liens de partages offrent les fonctionnalités suivantes :" %}</p>

{% blocktrans %}
<ul>
<li>partage de la dernière bêta ou du dernier brouillon ;</li>
<li>validité temporaire ou permanente ;</li>
<li>désactivation et réactivation à volonté.</li>
</ul>
{% endblocktrans %}

<a href="#create-shareable-link" class="open-modal">
{% trans "Créer un lien de partage" %}
</a>

<form
action="{% url 'content:create-shareable-link' content.pk %}"
method="post"
id="create-shareable-link"
class="modal modal-flex">
{% csrf_token %}
{{ create_form.as_p }}
<button type="submit" class="btn-submit">{% trans "Créer" %}</button>
</form>



<h2>{% trans "Liens actifs" %}</h2>

<p>
{% blocktrans %}
Les personnes disposant d'un lien actif peuvent l'utiliser pour lire le contenu.
Il est possible de désactiver un lien temporairement pour en interdire son usage, et le réactiver plus tard.
{% endblocktrans %}
</p>

{% if not active_links_and_forms %}

<p>{% trans "Vous n'avez pas de liens de partage actifs." %}</p>

{% else %}

<ul>
{% for link, edit_form in active_links_and_forms reversed %}
{% include 'tutorialv2/view/list_shareable_links.part.html' with link=link edit_form=edit_form content=content section="active" %}
{% endfor %}
</ul>

{% endif %}


<h2>{% trans "Liens expirés" %}</h2>

<p>
{% blocktrans %}
Un lien de partage expiré ne permet pas de lire le contenu.
Si un lien est expiré, vous pouvez modifier sa date d'expiration pour qu'il fonctionne de nouveau.
{% endblocktrans %}
</p>

{% if not expired_links_and_forms %}

<p>{% trans "Vous n'avez pas de liens de partage expirés." %}</p>

{% else %}

<ul>
{% for link, edit_form in expired_links_and_forms reversed %}
{% include 'tutorialv2/view/list_shareable_links.part.html' with link=link edit_form=edit_form content=content section="expired" %}
{% endfor %}
</ul>

{% endif %}

<h2>{% trans "Liens inactifs" %}</h2>

<p>
{% blocktrans %}
Un lien de partage inactif ne permet pas de lire le contenu.
Vous pouvez le réactiver quand vous le souhaitez pour autoriser de nouveau son usage.
{% endblocktrans %}
</p>

{% if not inactive_links_and_forms %}

<p>{% trans "Vous n'avez pas de liens de partage inactifs." %}</p>

{% else %}

<ul>
{% for link, edit_form in inactive_links_and_forms reversed %}
{% include 'tutorialv2/view/list_shareable_links.part.html' with link=link edit_form=edit_form content=content section="inactive" %}
{% endfor %}
</ul>

{% endif %}

{% endblock %}
76 changes: 76 additions & 0 deletions templates/tutorialv2/view/list_shareable_links.part.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{% load i18n %}

<li class="shareable-link">
<h3>{{ link.description }}</h3>
<input value="{{ link.full_url }}" />
<p>
{% if link.type == "DRAFT" %}
{% trans "Lien vers <em>le dernier brouillon</em>" %}
{% elif link.type == "BETA" %}
{% trans "Lien vers <em>la dernière bêta</em>" %}
{% else %}
{% trans "Lien de <em>type inconnu</em>" %}
{% endif %}
</p>
<p>
<p>
{% if link.expiration and link.expired %}
{% blocktrans with date=link.expiration %}
Lien <em>expiré</em> depuis le {{ date }}
{% endblocktrans %}
{% elif link.expiration and not link.expired %}
{% blocktrans with date=link.expiration %}
Valide <em>jusqu'au {{ date }}</em>
{% endblocktrans %}
{% else %}
{% trans "Valide <em>indéfiniment</em>" %}
{% endif %}
</p>
</p>
<a href="#edit-shareable-link-{{ link.id }}" class="open-modal">
{% trans "Modifier" %}
</a>

<form
action="{% url 'content:edit-shareable-link' link.id %}"
method="post"
id="edit-shareable-link-{{ link.id }}"
class="modal modal-flex">
{% csrf_token %}
{{ edit_form.as_p }}
<button type="submit" class="btn-submit">{% trans "Modifier" %}</button>
</form>


<a href="#delete-shareable-link-{{ link.id }}" class="open-modal">
{% trans "Supprimer" %}
</a>

<form
action="{% url 'content:delete-shareable-link' link.id %}"
method="post"
id="delete-shareable-link-{{ link.id }}"
class="modal modal-flex">
{% csrf_token %}
<p>
{% blocktrans %}
<strong>Attention, ce lien sera supprimé définitivement.</strong>
Vous ne pourrez pas le récupérer.
Si vous souhaitez réutiliser ce lien plus tard, désactivez-le.
{% endblocktrans %}
</p>
<button type="submit" class="btn-submit">{% trans "Supprimer" %}</button>
</form>

{% if section == "active" or section == "expired" %}
<form action="{% url 'content:deactivate-shareable-link' link.id %}" method="post">
{% csrf_token %}
<button type="submit" class="btn-cancel">{% trans "Désactiver" %}</button>
</form>
{% else %}
<form action="{% url 'content:reactivate-shareable-link' link.id %}" method="post">
{% csrf_token %}
<button type="submit" class="btn-submit">{% trans "Réactiver" %}</button>
</form>
{% endif %}
</li>
40 changes: 40 additions & 0 deletions zds/tutorialv2/migrations/0042_shareablelink.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Generated by Django 3.2.15 on 2022-09-29 22:07

from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

dependencies = [
("tutorialv2", "0041_remove_must_reindex"),
]

operations = [
migrations.CreateModel(
name="ShareableLink",
fields=[
("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
("active", models.BooleanField(default=True)),
("expiration", models.DateTimeField(null=True)),
("description", models.CharField(default="Lien de partage", max_length=150)),
(
"type",
models.CharField(
choices=[("DRAFT", "Lien vers le dernier brouillon"), ("BETA", "Lien vers la dernière bêta")],
default="DRAFT",
max_length=10,
),
),
(
"content",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="tutorialv2.publishablecontent",
verbose_name="Contenu",
),
),
],
),
]
4 changes: 2 additions & 2 deletions zds/tutorialv2/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from django.views.generic import DetailView, FormView
from django.views.generic import View

from zds.forum.models import Topic
from zds.tutorialv2.models.database import PublishableContent, PublishedContent, ContentRead
from zds.tutorialv2.utils import mark_read
from zds.tutorialv2.models.help_requests import HelpWriting
Expand Down Expand Up @@ -48,6 +47,7 @@ class SingleContentViewMixin:
sha = None
must_be_author = True
authorized_for_staff = True
authorized_for_all = False # used for shareable links
is_staff = False
is_author = False
must_redirect = False
Expand Down Expand Up @@ -97,7 +97,7 @@ def get_versioned_object(self):
is_beta = self.object.is_beta(self.sha)
is_public = self.object.is_public(self.sha) and self.public_is_prioritary

if not is_beta and not is_public and not self.is_author:
if not is_beta and not is_public and not self.is_author and not self.authorized_for_all:
if not self.is_staff or (not self.authorized_for_staff and self.must_be_author):
raise PermissionDenied

Expand Down
5 changes: 5 additions & 0 deletions zds/tutorialv2/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,8 @@
("REJECT", _("Rejeté")),
("CANCEL", _("Annulé")),
)

SHAREABLE_LINK_TYPES = (
("DRAFT", _("Lien vers le dernier brouillon")),
("BETA", _("Lien vers la dernière bêta")),
)
61 changes: 61 additions & 0 deletions zds/tutorialv2/models/shareable_links.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import uuid
from datetime import datetime

from django.conf import settings
from django.db import models
from django.db.models import Q
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from zds.tutorialv2.models import SHAREABLE_LINK_TYPES
from zds.tutorialv2.models.database import PublishableContent


class ShareableLinkQuerySet(models.QuerySet):
def for_content(self, content):
return self.filter(content=content)

def active_and_for_content(self, content):
return self.for_content(content).active()

def expired_and_for_content(self, content):
return self.for_content(content).expired()

def inactive_and_for_content(self, content):
return self.for_content(content).inactive()

def active(self):
pivot_date = datetime.now()
return self.filter(Q(active=True) & (Q(expiration__gte=pivot_date) | Q(expiration=None)))

def expired(self):
pivot_date = datetime.now()
return self.filter(active=True, expiration__lt=pivot_date)

def inactive(self):
return self.filter(active=False)


class ShareableLink(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
content = models.ForeignKey(PublishableContent, verbose_name="Contenu", on_delete=models.CASCADE)
active = models.BooleanField(default=True)
expiration = models.DateTimeField(null=True)
description = models.CharField(default=_("Lien de partage"), max_length=150)
# Types
# DRAFT: always points to the last draft version
# BETA: always points to the last beta version
type = models.CharField(max_length=10, choices=SHAREABLE_LINK_TYPES, default="DRAFT")

objects = ShareableLinkQuerySet.as_manager()

def full_url(self):
return settings.ZDS_APP["site"]["url"] + reverse("content:shareable-link-view", kwargs={"id": self.id})

def deactivate(self):
self.active = False
self.save()

def reactivate(self):
self.active = True
self.save()
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from django.test import TestCase
from django.urls import reverse

from zds.member.tests.factories import ProfileFactory, StaffProfileFactory
from zds.tutorialv2.models.shareable_links import ShareableLink
from zds.tutorialv2.tests.factories import PublishableContentFactory
from zds.tutorialv2.views.shareable_links import CreateShareableLinkView


class CreateShareableLinkTests(TestCase):
def setUp(self):
# Create users
self.author = ProfileFactory().user
self.staff = StaffProfileFactory().user
self.outsider = ProfileFactory().user

# Create a content
self.content = PublishableContentFactory(author_list=[self.author])

# Get information to be reused in tests
self.url = reverse("content:create-shareable-link", kwargs={"pk": self.content.pk})
self.redirect_url = reverse("content:list-shareable-links", kwargs={"pk": self.content.pk})
self.login_url = reverse("member-login") + "?next=" + self.url

def test_not_authenticated(self):
self.client.logout()
response = self.client.post(self.url)
self.assertRedirects(response, self.login_url)

def test_authenticated_author(self):
self.client.force_login(self.author)
n_links_before = ShareableLink.objects.all().count()
data = {"description": "Ceci n'est pas le lien vers La Blague", "expiration": "2042-08-01", "type": "BETA"}
response = self.client.post(self.url, data=data)
self.assertRedirects(response, self.redirect_url, target_status_code=200)
n_links_after = ShareableLink.objects.all().count()
self.assertEqual(n_links_after, n_links_before + 1)

def test_authenticated_staff(self):
self.client.force_login(self.staff)
response = self.client.post(self.url)
self.assertEqual(response.status_code, 403)

def test_authenticated_outsider(self):
self.client.force_login(self.outsider)
response = self.client.post(self.url)
self.assertEqual(response.status_code, 403)
Loading

0 comments on commit 0c025d4

Please sign in to comment.