[PYTHON] Divisez les données en unités de type projet avec Django (3)

Préface

Ceci est la suite de cet article. Une «unité de type projet» est un «projet» dans un outil de gestion de tâches, etc. Ici, puisque le théâtre est géré, la «performance» lui correspond.

---> Dépôt GitHub (Python 3.8.1, Django 3.0.3)

Contenu de l'article

La dernière fois, j'ai pensé à l'interface de saisie de données qui appartient à la "performance". Cette fois, j'aimerais réfléchir à un mécanisme pour inviter des utilisateurs autres que moi et les ajouter aux membres de la "performance".

Objectifs spécifiques

—— Permet au propriétaire d'une «performance» de créer une «invitation» pour un compte particulier.

Image complète

Ceux qui invitent

Invité

Organisez ce que vous voulez faire

Dans cette application Web, ["Performance User"](https://qiita.com/satamame/items/959e21ade18c48e1b4d6#%E5%85%AC%E6%BC%94%E3%83%A6%E3%83%BC% E3% 82% B6% E3% 81% AE% E3% 83% A2% E3% 83% 87% E3% 83% AB) aux comptes gérés par Django et à chaque «performance» Le statut de participation est associé. Par conséquent, le processus lorsque la personne invitée sélectionne «Rejoindre» est le processus de création d'un nouvel «utilisateur de performance».

Faire un modèle d '"invitation"

Il contient les champs suivants.

champ Contenu
production Invité "performance"
inviter Personne invitante(Comptes gérés par Django)
invitee Personne invitée(Comptes gérés par Django)
exp_dt Date d'expiration de l'invitation

Créer un mécanisme pour créer une "invitation"

--Il y a un écran appelé "Liste des membres", alors créez un bouton appelé "Inviter" ici. --Lorsque vous appuyez sur ce bouton, l'écran de création d'une «invitation» s'affiche. ――Sur l'écran pour créer une «invitation», vous devez pouvoir spécifier la «personne invitée» (le contenu des autres champs est décidé par l'application).

Créer un mécanisme pour afficher "invitation"

--Lorsque le propriétaire de "Performance" affiche l'écran "Liste des membres", la "Carte d'invitation" qu'il a créée sera affichée.

Créer un mécanisme pour participer à des «performances» à partir d '«invitations»

Modèle d'invitation

from datetime import datetime, timezone
from django.conf import settings
from django.db import models


class Invitation(models.Model):
    '''Invitation à la troupe
    '''
    production = models.ForeignKey(Production, verbose_name='Performance',
        on_delete=models.CASCADE)
    inviter = models.ForeignKey(settings.AUTH_USER_MODEL,
        verbose_name='Personne invitante',
        related_name='inviter', on_delete=models.CASCADE)
    invitee = models.ForeignKey(settings.AUTH_USER_MODEL,
        verbose_name='Personne invitée',
        related_name='invitee', on_delete=models.CASCADE)
    exp_dt = models.DateTimeField(verbose_name='Date limite')
    
    class Meta:
        verbose_name = verbose_name_plural = 'Invitation à la troupe'
        ordering = ['exp_dt']
    
    def __str__(self):
        return f'{self.invitee}À{self.production}Invitation à'
    
    def expired(self):
        '''Renvoie si cette invitation a expiré
        '''
        now = datetime.now(timezone.utc)
        return now > self.exp_dt

--Production est défini dans cette application Web (https://qiita.com/satamame/items/959e21ade18c48e1b4d6#%E5%85%AC%E6%BC%94-%E3%83%97%E3% 83% AD% E3% 82% B8% E3% 82% A7% E3% 82% AF% E3% 83% 88% E7% 9A% 84% E5% 8D% 98% E4% BD% 8D-% E3% 81 % AE% E3% 83% A2% E3% 83% 87% E3% 83% AB) Il s'agit d'un modèle de «performance». ―― Puisque ʻinviter et ʻinvitee sont des comptes gérés par Django, ils font référence à l'objet de settings.AUTH_USER_MODEL.

Mécanisme de création d'invitations

Vous trouverez ci-dessous une description des vues et des modèles. Puisqu'il est créé dans une application appelée «production», l'URL est spécifiée comme «production: usr_list». Voir Source pour production / urls.py pour savoir comment écrire ʻurls.py` pour l'appel. S'il vous plaît.

Créer une vue

Créez une vue qui hérite de «CreateView» pour créer une invitation.

views.py


from django.views.generic.edit import CreateView
from django.contrib import messages
from django.contrib.auth import get_user_model
from .view_func import *
from .models import Production, ProdUser, Invitation


class InvtCreate(LoginRequiredMixin, CreateView):
    '''Vue supplémentaire de l'invitation
    '''
    model = Invitation
    fields = ()

views.py


    def get(self, request, *args, **kwargs):
        '''Handler pour recevoir la demande au moment de l'affichage
        '''
        #Inspectez la propriété pour accéder aux utilisateurs performants
        prod_user = test_owner_permission(self)
        
        #Avoir la production comme attribut de vue
        #Pour afficher comme élément fixe dans le modèle
        self.production = prod_user.production
        
        return super().get(request, *args, **kwargs)

views.py


    def post(self, request, *args, **kwargs):
        '''Le gestionnaire recevra la demande lors de l'enregistrement
        '''
        #Inspectez la propriété pour accéder aux utilisateurs performants
        prod_user = test_owner_permission(self)
        
        #"ID de la personne invitée" saisi dans le formulaire
        invitee_value = request.POST['invitee_id']
        
        #Avoir un utilisateur correspondant comme attribut de vue
        user_model = get_user_model()
        invitees = user_model.objects.filter(username=invitee_value)
        if len(invitees) > 0:
            self.invitee = invitees[0]
        
        # prod_user,Avoir la production comme attribut de vue
        #À utiliser lors de la validation et du stockage
        self.prod_user = prod_user
        self.production = prod_user.production
        
        return super().post(request, *args, **kwargs)

――Sur l'écran de création d'une invitation, dans le cadre de l'interface utilisateur, la "personne invitée" est écrite comme la "personne invitée".

views.py


    def form_valid(self, form):
        '''Une fois la validation réussie
        '''
        #L'ajout a échoué si le gestionnaire POST n'a pas réussi à inviter l'utilisateur
        if not hasattr(self, 'invitee'):
            return self.form_invalid(form)
        
        #Liste des ID utilisateur des utilisateurs performants
        prod_users = ProdUser.objects.filter(production=self.production)
        prod_user_user_ids = [prod_user.user.id for prod_user in prod_users]
        
        #Liste d'identifiants des utilisateurs invités
        invitations = Invitation.objects.filter(production=self.production)
        current_invitee_ids = [invt.invitee.id for invt in invitations]
        
        #Il n'est pas possible d'inviter des utilisateurs performants ou des utilisateurs invités.
        if self.invitee.id in prod_user_user_ids\
            or self.invitee.id in current_invitee_ids:
            return self.form_invalid(form)
        
        #Définissez chaque champ de l'enregistrement que vous souhaitez ajouter
        instance = form.save(commit=False)
        instance.production = self.production
        instance.inviter = self.prod_user.user
        instance.invitee = self.invitee
        #Le délai est de 7 jours
        #Enregistré en UTC par défaut, mais spécifiez UTC juste au cas où
        instance.exp_dt = datetime.now(timezone.utc) + timedelta(days=7)
        
        messages.success(self.request, str(instance.invitee) + "Était invité.")
        return super().form_valid(form)

views.py


    def get_success_url(self):
        '''Donner dynamiquement la destination de la transition lorsque l'ajout est réussi
        '''
        prod_id = self.prod_user.production.id
        url = reverse_lazy('production:usr_list', kwargs={'prod_id': prod_id})
        return url
    
    def form_invalid(self, form):
        '''Lorsque l'ajout échoue
        '''
        messages.warning(self.request, "Je ne pourrais pas vous inviter.")
        return super().form_invalid(form)

--get_success_url ()etform_invalid ()en cas d'échec.

Créer un modèle

invitation_form.html


{% extends 'base.html' %}

{% block content %}
<h1 style="margin: 0px;">
<a href="{% url 'production:usr_list' prod_id=view.production.id %}">◀</a>
Invitation à la troupe
</h1>

<div>&nbsp;</div>

<form method="post">
    {% csrf_token %}
    <table>
    <tr><th>Performance</th><td>{{ view.production }}</td></tr>
    <tr><th>ID de la personne à inviter</th><td><input type="text" name="invitee_id" /></td></tr>
    </table>
    <input type="submit" value="Invitation">
</form>
{% endblock %}

Mécanisme pour afficher l'invitation créée

L'écran qui affiche l'invitation que vous avez créée est l'écran "Liste des membres". Nous allons également mettre en place un bouton "inviter" ici. Le scénario utilisateur est le suivant.

  1. Appuyez sur "Inviter" sur l'écran "Liste des membres" pour chaque représentation.
  2. La vue (créée ci-dessus) pour créer l'invitation s'affiche.
  3. Soumettez le formulaire en saisissant l'identifiant de l'utilisateur que vous invitez.
  4. Vous pouvez revenir à l'écran "Liste des membres" et vérifier l'invitation que vous avez faite.

Créer une vue

views.py


from django.views.generic import ListView
from django.core.exceptions import PermissionDenied


class UsrList(LoginRequiredMixin, ListView):
    '''Vue de la liste des producteurs
    '''
    model = ProdUser
    
    def get(self, request, *args, **kwargs):
        '''Handler pour recevoir la demande au moment de l'affichage
        '''
        #Obtenez des utilisateurs performants à partir des informations d'accès et inspectez les droits d'accès
        prod_user = accessing_prod_user(self, kwargs['prod_id'])
        if not prod_user:
            raise PermissionDenied
        
        #Faites-en un attribut de vue afin qu'il puisse être référencé à partir du modèle
        self.prod_user = prod_user
        
        #Faites-en un attribut de la vue pour afficher les utilisateurs invités
        self.invitations = Invitation.objects.filter(production=prod_user.production)
        
        return super().get(request, *args, **kwargs)
    
    #Ce qui suit est omis

――Originalement, c'est une vue qui affiche une liste d '"utilisateurs de performance", mais afin d'afficher une liste d'invitations sur le même écran, les invitations sont définies sur l'attribut Vue (ʻinvitations`) afin qu'elles puissent être référencées à partir du modèle. ――Dans la section précédente, j'ai écrit "Carte d'invitation que j'ai faite", mais à proprement parler, j'ai obtenu "Carte d'invitation pour cette représentation". La raison est qu'il sera affiché même si le propriétaire de la "performance" change.

Créer un modèle

Seule la partie affichant l'invitation est extraite. Pour tout voir, accédez à Code sur GitHub.

produser_list.html


{% if view.prod_user.is_owner and view.invitations %}
<div style="border:solid thin lightgray; border-radius:10px; padding: 10px; margin:5px 0 20px;">
<p><strong>Inviter un utilisateur</strong></p>
<table>
    <tr>
        <th>Identifiant d'utilisateur</th>
        <th>Nom de famille</th>
        <th>Nom</th>
        <th>Date limite d'invitation</th>
    </tr>
    {% for item in view.invitations %}
    <tr>
        <td>{{ item.invitee.username }}</td>
        <td>{{ item.invitee.last_name }}</td>
        <td>{{ item.invitee.first_name }}</td>
        {% if item.expired %}
            <td style="color:red;">{{ item.exp_dt }}</td>
        {% else %}
            <td>{{ item.exp_dt }}</td>
        {% endif %}
        <td><a href="{% url 'production:invt_delete' pk=item.id from='usr_list' %}" class="deletelink">Effacer</a></td>
    </tr>
{% endfor %}
</table>
</div>
{% endif %}

--Dans la première instruction ʻif, affiche la liste des invitations à condition que l'utilisateur accédant soit le propriétaire de la performance et que la vue ait l'attribut ʻinvitations (et ne soit pas vide). Je le fais.

Mécanisme d'affichage des invitations reçues

L'écran qui affiche l'invitation que vous avez reçue est l'écran «Performances participantes». À l'origine, il s'agit d'un écran qui affiche une liste des performances participantes, mais tout comme lors de l'affichage de l'invitation créée, la zone de l'invitation n'est affichée que lorsque les conditions sont remplies.

Créer une vue

views.py


class ProdList(LoginRequiredMixin, ListView):
    '''Vue de la liste de production
    
Le modèle est ProdUser car seules les performances de l'utilisateur connecté sont affichées.
    '''
    model = ProdUser
    template_name = 'production/production_list.html'
    
    def get(self, request, *args, **kwargs):
        '''Handler pour recevoir la demande au moment de l'affichage
        '''
        #Faites-en un attribut de vue pour afficher les invitations à la troupe
        now = datetime.now(timezone.utc)
        self.invitations = Invitation.objects.filter(invitee=self.request.user,
            exp_dt__gt=now)
        
        return super().get(request, *args, **kwargs)
    
    #Ce qui suit est omis

Créer un modèle

Seule la partie affichant l'invitation est extraite. Pour tout voir, accédez à Code sur GitHub.

production_list.html


{% if view.invitations %}
<div style="border:solid thin lightgray; border-radius:10px; padding: 10px; margin:5px 0 20px;">
<p><strong>Groupe invité</strong></p>
<table>
    <tr>
        <th>Nom de la performance</th>
        <th>ID de l'invitant</th>
        <th>Date limite d'invitation</th>
    </tr>
    {% for item in view.invitations %}
    <tr>
        <td>{{ item.production }}</td>
        <td>{{ item.inviter.username }}</td>
        {% if item.expired %}
            <td style="color:red;">{{ item.exp_dt }}</td>
        {% else %}
            <td>{{ item.exp_dt }}</td>
        {% endif %}
        <td>
            <a href="{% url 'production:prod_join' invt_id=item.id %}" class="addlink">Participation</a>
            <a href="{% url 'production:invt_delete' pk=item.id from='prod_list' %}" class="deletelink">Effacer</a>
        </td>
    </tr>
{% endfor %}
</table>
</div>
{% endif %}

--En plus du bouton "Supprimer", le bouton "Rejoindre" sera affiché sur l'invitation reçue. --Lorsque vous appuyez sur le bouton «Rejoindre», vous serez redirigé vers la vue qui confirme et traite votre participation.

Mécanisme de suppression des invitations

Lors de l'affichage des invitations que j'ai faites et des invitations que j'ai reçues, j'ai placé un bouton «Supprimer» dans le modèle. Créez une vue à laquelle vous pouvez accéder à partir de ce lien (production: invoke_delete).

Créer une vue

views.py


from django.views.generic.edit import DeleteView


class InvtDelete(LoginRequiredMixin, DeleteView):
    '''Vue de suppression d'invitation
    '''
    model = Invitation
    template_name_suffix = '_delete'

-- template_name_suffix semble être "_confirm_delete "par défaut. Cette zone est votre choix.

views.py


    def get(self, request, *args, **kwargs):
        '''Handler pour recevoir la demande au moment de l'affichage
        '''
        #Vérifiez que vous êtes le propriétaire du spectacle ou l'invité de l'invitation
        invt = self.get_object()
        prod_id = invt.production.id
        prod_user = accessing_prod_user(self, prod_id)
        
        is_owner = prod_user and prod_user.is_owner
        is_invitee = self.request.user == invt.invitee
        
        if not (is_owner or is_invitee):
            raise PermissionDenied
        
        return super().get(request, *args, **kwargs)

views.py


    def post(self, request, *args, **kwargs):
        '''Le gestionnaire recevra la demande lors de l'enregistrement
        '''
        #Vérifiez que vous êtes le propriétaire du spectacle ou l'invité de l'invitation
        invt = self.get_object()
        prod_id = invt.production.id
        prod_user = accessing_prod_user(self, prod_id)
        
        is_owner = prod_user and prod_user.is_owner
        is_invitee = self.request.user == invt.invitee
        
        if not (is_owner or is_invitee):
            raise PermissionDenied
        
        return super().post(request, *args, **kwargs)

views.py


    def get_success_url(self):
        '''Donner dynamiquement la destination de la transition lorsque la suppression est réussie
        '''
        if self.kwargs['from'] == 'usr_list':
            prod_id = self.object.production.id
            url = reverse_lazy('production:usr_list', kwargs={'prod_id': prod_id})
        else:
            url = reverse_lazy('production:prod_list')
        return url
    
    def delete(self, request, *args, **kwargs):
        '''Message une fois supprimé
        '''
        result = super().delete(request, *args, **kwargs)
        messages.success(
            self.request, str(self.object) + "A été supprimé.")
        return result

Créer un modèle

C'est une page simple avec seulement un bouton «Supprimer» et un bouton «Annuler».

invitation_delete.html


{% extends 'base.html' %}

{% block content %}
<h1 style="margin: 0px;">
{% if view.kwargs.from == 'usr_list' %}
<a href="{% url 'production:usr_list' prod_id=object.production.id %}">◀</a>
{% else %}
<a href="{% url 'production:prod_list' %}">◀</a>
{% endif %}
Supprimer l'invitation à la troupe
</h1>

<form method="post">{% csrf_token %}
    <p>Voulez-vous supprimer cette invitation?</p>
    <input type="submit" value="effacer">
    <input type="button"
        {% if view.kwargs.from == 'usr_list' %}
        onclick="location.href='{% url 'production:usr_list' prod_id=object.production.id %}'"
        {% else %}
        onclick="location.href='{% url 'production:prod_list' %}'"
        {% endif %}
        value="Annuler">
</form>

<table>
    <tr><th>Performance</th><td>{{ object.production }}</td></tr>
    <tr><th>ID de la personne invitée</th><td>{{ object.invitee.username }}</td></tr>
    <tr><th>Nom de famille</th><td>{{ object.invitee.last_name }}</td></tr>
    <tr><th>Nom</th><td>{{ object.invitee.first_name }}</td></tr>
    <tr><th>Date limite</th>
    {% if object.expired %}
        <td style="color:red;">{{ object.exp_dt }}</td>
    {% else %}
        <td>{{ object.exp_dt }}</td>
    {% endif %}
    </tr>
</table>
{% endblock %}

Mécanisme pour participer à la performance à partir de l'invitation

Créer une vue

Le processus lorsque "Rejoindre" est sélectionné est le processus de création d'un nouvel "Utilisateur de performance", donc créez le CreateView suivant. Cependant, le nom de Template est clairement nommé "production_join.html".

views.py


from django.http import Http404


class ProdJoin(LoginRequiredMixin, CreateView):
    '''Voir pour participer à la performance
    '''
    model = ProdUser
    fields = ('production', 'user')
    template_name = 'production/production_join.html'
    success_url = reverse_lazy('production:prod_list')

views.py


    def get(self, request, *args, **kwargs):
        '''Handler pour recevoir la demande au moment de l'affichage
        '''
        #Vérifiez si vous êtes invité et obtenez une performance à laquelle vous pouvez participer
        self.production = self.production_to_join()
        
        return super().get(request, *args, **kwargs)
    
    def post(self, request, *args, **kwargs):
        '''Le gestionnaire recevra la demande lors de l'enregistrement
        '''
        #Vérifiez si vous êtes invité et obtenez une performance à laquelle vous pouvez participer
        self.production = self.production_to_join()
        
        return super().post(request, *args, **kwargs)

--Dans les gestionnaires GET et POST, en fonction des informations de ʻURLConf`, définissez les performances auxquelles participer en tant qu'attribut.

views.py


    def production_to_join(self):
        '''Vérifiez si vous êtes invité et renvoyez un spectacle auquel vous pouvez assister
        '''
        #Lancer une erreur 404 si vous n'êtes pas invité
        invt_id = self.kwargs['invt_id'];
        invts = Invitation.objects.filter(id=invt_id)
        if len(invts) < 1:
            raise Http404
        invt = invts[0]
        
        #Lancer une erreur 404 si l'invitation a expiré
        now = datetime.now(timezone.utc)
        if now > invt.exp_dt:
            raise Http404
        
        #PermissionDenied si l'utilisateur accédant n'est pas invité
        user = self.request.user
        if user != invt.invitee:
            raise PermissionDenied
        
        return invt.production

-Au ["Mécanisme d'affichage des invitations reçues"](#Mécanisme d'affichage des invitations reçues), le paramètre ʻinvt_id est intégré dans le lien du bouton "Rejoindre", donc invitez à utiliser ceci. Nous obtenons un objet d'état (ʻInvitation).

views.py


    def form_valid(self, form):
        '''Une fois la validation réussie
        '''
        #Inspecter l'invitation
        invt_id = self.kwargs['invt_id'];
        invts = Invitation.objects.filter(id=invt_id)
        if len(invts) < 1:
            return self.form_invalid(form)
        invt = invts[0]
        
        #Obtenez l'enregistrement à sauvegarder
        new_prod_user = form.save(commit=False)
        
        #Les performances correctes sont-elles définies?
        if new_prod_user.production != invt.production:
            return self.form_invalid(form)
        
        #L'utilisateur correct est-il défini?
        if new_prod_user.user != invt.invitee:
            return self.form_invalid(form)
        
        #Supprimer l'invitation
        invt.delete()
        
        messages.success(self.request, str(invt.production) + "J'ai participé à.")
        return super().form_valid(form)
    
    def form_invalid(self, form):
        '''Lorsque vous ne parvenez pas à participer
        '''
        messages.warning(self.request, "Je n'ai pas pu participer.")
        return super().form_invalid(form)

Créer un modèle

C'est une page simple avec seulement un bouton «Rejoindre» et un bouton «Plus tard».

production_join.html


{% extends 'base.html' %}

{% block content %}
<h1 style="margin: 0px;">
<a href="{% url 'production:prod_list' %}">◀</a>
Participez à la performance
</h1>

<div>&nbsp;</div>

<p>
{{ view.production }}Ont été invités à. Etes-vous sûr de vouloir participer?
</p>

<form method="post">
    {% csrf_token %}
    <input type="hidden" name="production" value="{{ view.production.id }}">
    <input type="hidden" name="user" value="{{ view.request.user.id }}">
    <input type="submit" value="Participation">
    <input type="button"
        onclick="location.href='{% url 'production:prod_list' %}'"
        value="plus tard">
</form>
{% endblock %}

Résumé

Nous avons introduit l'implémentation de la fonction d'inviter les utilisateurs à une «unité de projet» (groupe) appelée «performance» et de permettre aux utilisateurs invités d'y participer. Les techniques utilisées sont résumées ci-dessous.

Recommended Posts

Divisez les données en unités de type projet avec Django (3)
Divisez les données en unités de type projet avec Django
Divisez les données en unités de type projet avec Django (2)
Utilisez Django pour enregistrer les données de tweet
Gérez vos données avec AWS RDS
Diviser le japonais (katakana) en syllabes [Python]
Création de la première application avec Django startproject
Transformez les données de vacances en une trame de données avec les pandas
Entraînez Stanford NER Tagger avec vos propres données
Internationalisation avec Django
CRUD avec Django
Essayez de diviser les données Twitter en SPAM et HAM
Authentification unique à l'application Django avec AWS SSO
Divisez l'ensemble de données (ndarray) en proportions arbitraires avec NumPy