[PYTHON] Divide your data into project-like units with Django (3)

Preface

This is a continuation of this article. A "project-like unit" is a "project" in a task management tool, etc., and here it is a "performance" because it manages plays.

---> GitHub repository (Python 3.8.1, Django 3.0.3)

Article content

Last time, I thought about the data input interface that belongs to "performance". This time, I would like to think about a mechanism to invite users other than myself and add them to the members of the "performance".

--The techniques used are summarized in "Summary". --There is a working demo on Heroku. See GitHub Pages for more information.

Specific goals

--Allows "performance" owners to create "invitations" for specific accounts. ――You can check or delete the created "invitation card". --The "invitation" will be displayed when you log in with the invited account. --You will be able to participate in "performances" from the "invitations" you received, and delete the "invitations" you received. ――We will make it a mechanism that is completed within the web application without using e-mail etc.

Complete image

Those who invite

Invited

Organize what you want to do

In this web app, ["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) to Django-managed accounts and each "performance" The participation status is associated. Therefore, the process when the invited person selects "Join" is the process of creating a new "Performance User".

Make a model of "invitation"

It has the following fields.

field Contents
production Invited "performance"
inviter Inviting person(Accounts managed by Django)
invitee Invited person(Accounts managed by Django)
exp_dt Invitation expiration date

Create a mechanism to create "invitations"

--There is a screen called "Member List", so create a button called "Invite" there. --When you press this button, the screen for creating an "invitation" will be displayed. ――On the screen to create an "invitation", you need to be able to specify the "invited person" (the contents of other fields are decided by the app).

Create a mechanism to display "invitations"

--When the owner of "Performance" displays the "Member List" screen, the "Invitation Card" that he created will be displayed. --You can also delete it here. --When the invited person displays the "Participating Performances" screen (the screen that is displayed immediately after logging in), the "Invitation Card" addressed to you will be displayed. --However, those that have passed the expiration date will not be displayed. ――Here, you can participate in the "performance" and delete the "invitation card".

Create a mechanism to participate in "performances" from "invitations"

--The process when the invited person selects "Join" is the process of creating a new "Performance User".

Invitation model

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


class Invitation(models.Model):
    '''Invitation to the troupe
    '''
    production = models.ForeignKey(Production, verbose_name='Performance',
        on_delete=models.CASCADE)
    inviter = models.ForeignKey(settings.AUTH_USER_MODEL,
        verbose_name='Inviting person',
        related_name='inviter', on_delete=models.CASCADE)
    invitee = models.ForeignKey(settings.AUTH_USER_MODEL,
        verbose_name='Invited person',
        related_name='invitee', on_delete=models.CASCADE)
    exp_dt = models.DateTimeField(verbose_name='Deadline')
    
    class Meta:
        verbose_name = verbose_name_plural = 'Invitation to the troupe'
        ordering = ['exp_dt']
    
    def __str__(self):
        return f'{self.invitee}To{self.production}Invitation to'
    
    def expired(self):
        '''Returns whether this invitation has expired
        '''
        now = datetime.now(timezone.utc)
        return now > self.exp_dt

--Production is defined in this web app (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) This is a model of "performance". ――Since both ʻinviter and ʻinvitee are accounts managed by Django, they are references to the object in settings.AUTH_USER_MODEL. --Please see here for ʻAUTH_USER_MODEL`.

Mechanism for creating invitations

The following is a description of View and Template. Since it is created in an application called production, the URL is specified as'production: usr_list'. See Source for production / urls.py for how to write ʻurls.py` for the call. please.

Create a View

Create a View that inherits CreateView to create an 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):
    '''Additional view of Invitation
    '''
    model = Invitation
    fields = ()

--The reason for emptying fields, which may be a bit tricky, is to set all the values of the fields (except ʻid) in code instead of Form`.

views.py


    def get(self, request, *args, **kwargs):
        '''Handler to receive the request at the time of display
        '''
        #Inspect ownership to get accessing performance users
        prod_user = test_owner_permission(self)
        
        #Have production as an attribute of view
        #To display as a fixed element in the template
        self.production = prod_user.production
        
        return super().get(request, *args, **kwargs)

--We call a method called test_owner_permission () to allow access only to users who own the performance. --See Source for production / view_func.py for what this method does. ..

views.py


    def post(self, request, *args, **kwargs):
        '''Handler to receive request at save
        '''
        #Inspect ownership to get accessing performance users
        prod_user = test_owner_permission(self)
        
        #"Invited person ID" entered in the form
        invitee_value = request.POST['invitee_id']
        
        #Have a matching user as an attribute of view
        user_model = get_user_model()
        invitees = user_model.objects.filter(username=invitee_value)
        if len(invitees) > 0:
            self.invitee = invitees[0]
        
        # prod_user,Have production as an attribute of view
        #For use during validation and storage
        self.prod_user = prod_user
        self.production = prod_user.production
        
        return super().post(request, *args, **kwargs)

--On the screen for creating invitations, "invited person" is written as "invited person" in the context of the UI. --Since the "invited person" is of type ForeignKey, normally Form is used to display the selectable UI, but I'm wondering if all users can be selected, so the account ID (text The actual field name is ʻusername). --The process of finding the user's object based on the entered ID is done by the post () method. --ʻUser Use get_user_model () to reference the model (> Documents -model)). This is because settings.AUTH_USER_MODEL is a string. --I try to set the View's ʻinvitee` attribute only when it is found, and check for its existence at the time of validation.

views.py


    def form_valid(self, form):
        '''When passed validation
        '''
        #Add failed if the POST handler did not get the user to invite
        if not hasattr(self, 'invitee'):
            return self.form_invalid(form)
        
        #User ID list of performance users
        prod_users = ProdUser.objects.filter(production=self.production)
        prod_user_user_ids = [prod_user.user.id for prod_user in prod_users]
        
        #ID list of invited users
        invitations = Invitation.objects.filter(production=self.production)
        current_invitee_ids = [invt.invitee.id for invt in invitations]
        
        #It is not possible to invite performance users or invited users.
        if self.invitee.id in prod_user_user_ids\
            or self.invitee.id in current_invitee_ids:
            return self.form_invalid(form)
        
        #Set each field of the record you want to add
        instance = form.save(commit=False)
        instance.production = self.production
        instance.inviter = self.prod_user.user
        instance.invitee = self.invitee
        #Deadline is 7 days
        #Saved in UTC by default, but specify UTC just in case
        instance.exp_dt = datetime.now(timezone.utc) + timedelta(days=7)
        
        messages.success(self.request, str(instance.invitee) + "Was invited.")
        return super().form_valid(form)

--Since fields is empty (because Form has nothing to validate), any user trying to create an invitation will go through this method. --Checks for the existence of a user with the specified ID and whether the user has already joined or been invited. --If you set all the fields and call the super method, the created invitation will be saved.

views.py


    def get_success_url(self):
        '''Dynamically give the transition destination when the addition is successful
        '''
        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):
        '''When the addition fails
        '''
        messages.warning(self.request, "I couldn't invite you.")
        return super().form_invalid(form)

-- get_success_url () and form_invalid () when it fails. --The return destination (production: user_list) is the View ("Member List" screen) where you are trying to place the "Invite" button.

Create Template

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 to the troupe
</h1>

<div>&nbsp;</div>

<form method="post">
    {% csrf_token %}
    <table>
    <tr><th>Performance</th><td>{{ view.production }}</td></tr>
    <tr><th>ID of the person to invite</th><td><input type="text" name="invitee_id" /></td></tr>
    </table>
    <input type="submit" value="Invitation">
</form>
{% endblock %}

--Just enter ʻinvitee_id` and submit with the "Invite" button.

Mechanism to display the created invitation

The screen that displays the invitations you created is the "Member List" screen. We will also set up an "invite" button here. The user scenario is as follows.

  1. Press "Invite" on the "Member List" screen for each performance.
  2. The View (created above) for creating the invitation is displayed.
  3. Submit the form by entering the ID of the user you are inviting.
  4. You can return to the "Member List" screen and check the invitations you have created.

Create a View

views.py


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


class UsrList(LoginRequiredMixin, ListView):
    '''ProdUser list view
    '''
    model = ProdUser
    
    def get(self, request, *args, **kwargs):
        '''Handler to receive the request at the time of display
        '''
        #Obtain performance users from access information and inspect access rights
        prod_user = accessing_prod_user(self, kwargs['prod_id'])
        if not prod_user:
            raise PermissionDenied
        
        #Make it a view attribute so that it can be referenced from the template
        self.prod_user = prod_user
        
        #Make it an attribute of the view to show the invited users
        self.invitations = Invitation.objects.filter(production=prod_user.production)
        
        return super().get(request, *args, **kwargs)
    
    #The following is omitted

--Originally, it is a View that displays a list of "performance users", but in order to display a list of invitations on the same screen, the invitations are set to View attributes (ʻinvitations`) so that they can be referenced from the Template. ――In the previous section, I wrote "invitation card I made", but strictly speaking, I have obtained "invitation card inviting to this performance". The reason is that it will be displayed even if the owner of the "performance" changes.

Create Template

Only the part displaying the invitation is excerpted. To see the whole thing, go to Code on 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>Inviting user</strong></p>
<table>
    <tr>
        <th>User ID</th>
        <th>Surname</th>
        <th>Name</th>
        <th>Invitation deadline</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">Delete</a></td>
    </tr>
{% endfor %}
</table>
</div>
{% endif %}

--In the first ʻif statement, display the list of invitations provided that the accessing user is the owner of the performance and the View has the ʻinvitations attribute (and is not empty). I am doing it. --I want to display it in red when the invitation has expired, so I'm using the ʻexpired method of the ʻInvitation class created above. --The point is that you can call the method from Template. --Each invitation will have a "Delete" button to jump to the View you're about to delete. ――Invited people can also delete using the same View, so I added the parameter from to the URL so that I can remember the return destination.

Mechanism for displaying received invitations

The screen that displays the invitations you receive is the "Participating Performances" screen. Originally, it is a screen that displays a list of participating performances, but just like when displaying the created invitation, the area for the invitation is displayed only when the conditions are met.

Create a View

views.py


class ProdList(LoginRequiredMixin, ListView):
    '''Production list view
    
The model is ProdUser because only the performances of the logged-in user are displayed.
    '''
    model = ProdUser
    template_name = 'production/production_list.html'
    
    def get(self, request, *args, **kwargs):
        '''Handler to receive the request at the time of display
        '''
        #Make it an attribute of the view to display invitations to the 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)
    
    #The following is omitted

--The invitation is set as an attribute of View, just like when displaying the created invitation. ――The difference from displaying the created invitation is that only the invitations that have not expired are taken out from the beginning. --ʻExpired () method is not used, but it is extracted by filter () of QuerySet. --See [here](https://docs.djangoproject.com/en/3.0/ref/models/querysets/#field-lookups) for how to write conditions such as xx__gt = xx` as you are doing here. Please give me.

Create Template

Only the part displaying the invitation is excerpted. To see the whole thing, go to Code on GitHub.

production_list.html


{% if view.invitations %}
<div style="border:solid thin lightgray; border-radius:10px; padding: 10px; margin:5px 0 20px;">
<p><strong>Invited group</strong></p>
<table>
    <tr>
        <th>Performance name</th>
        <th>Inviter ID</th>
        <th>Invitation deadline</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">Delete</a>
        </td>
    </tr>
{% endfor %}
</table>
</div>
{% endif %}

--In addition to the "Delete" button, the "Join" button will be displayed on the received invitation. --When you press the "Join" button, you will jump to the View that confirms and processes your participation.

Mechanism for deleting invitations

When displaying the invitations I made and the invitations I received, I placed a "Delete" button in the Template. Create a View that you can access from that link (production: invoke_delete).

Create a View

views.py


from django.views.generic.edit import DeleteView


class InvtDelete(LoginRequiredMixin, DeleteView):
    '''Invitation delete view
    '''
    model = Invitation
    template_name_suffix = '_delete'

--template_name_suffix seems to default to'_confirm_delete'. This area is your choice.

views.py


    def get(self, request, *args, **kwargs):
        '''Handler to receive the request at the time of display
        '''
        #Check that you are the owner of the performance or the invitee of the 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)

--The ID of the "performance" is taken from the object (invitation) to be deleted, and it is checked that the accessing user is the owner of the performance or the invited person. --ʻAccessing_prod_user ()For what the method does, see [Source forproduction / view_func.py`](https://github.com/satamame/pscweb2/blob/master/production/view_func.py) Please see.

views.py


    def post(self, request, *args, **kwargs):
        '''Handler to receive request at save
        '''
        #Check that you are the owner of the performance or the invitee of the 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)

--The same thing that the POST handler does is the same as GET. If you feel uncomfortable with duplication, you can make it a method.

views.py


    def get_success_url(self):
        '''Dynamically give the transition destination when the deletion is successful
        '''
        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 when deleted
        '''
        result = super().delete(request, *args, **kwargs)
        messages.success(
            self.request, str(self.object) + "Has been deleted.")
        return result

--Since the from parameter is added to the link URL of the" Delete "button, the return destination is sorted using it. --You can access ʻURLConf with self.kwargseven if you are not in theget ()orpost () methods. --ʻURLConf is specified in production / urls.py as follows (> Source ).

```path('invt_delete/<int:pk>/<str:from>/', views.InvtDelete.as_view(), name='invt_delete'),```

Create Template

It is a simple page with only a "Delete" button and a "Cancel" button.

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 %}
Remove invitation to troupe
</h1>

<form method="post">{% csrf_token %}
    <p>Do you want to delete this invitation?</p>
    <input type="submit" value="delete">
    <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="Cancel">
</form>

<table>
    <tr><th>Performance</th><td>{{ object.production }}</td></tr>
    <tr><th>ID of the invited person</th><td>{{ object.invitee.username }}</td></tr>
    <tr><th>Surname</th><td>{{ object.invitee.last_name }}</td></tr>
    <tr><th>Name</th><td>{{ object.invitee.first_name }}</td></tr>
    <tr><th>Deadline</th>
    {% if object.expired %}
        <td style="color:red;">{{ object.exp_dt }}</td>
    {% else %}
        <td>{{ object.exp_dt }}</td>
    {% endif %}
    </tr>
</table>
{% endblock %}

--You may want to hide the "ID of the invited person" if from is'prod_list' (if the invited person opens it). --To access ʻURLConfin the Template, use something likeview.kwargs.from` (if'from'is the parameter name).

Mechanism to participate in the performance from the invitation

Create a View

The process when "Join" is selected is the process of creating a new "Performance User", so create the following CreateView. However, the name of the Template is'production_join.html' for clarity.

views.py


from django.http import Http404


class ProdJoin(LoginRequiredMixin, CreateView):
    '''View to participate in the performance
    '''
    model = ProdUser
    fields = ('production', 'user')
    template_name = 'production/production_join.html'
    success_url = reverse_lazy('production:prod_list')

--The production and ʻuser specified in fieldsare not input by the user, but they are hidden and embedded in the Template so that they can be received fromForm`. ――Of course, you can set it with the code as you did in ["Mechanism for creating invitations"](#Mechanism for creating invitations).

views.py


    def get(self, request, *args, **kwargs):
        '''Handler to receive the request at the time of display
        '''
        #Inspect if you are invited and get a performance that you can participate in
        self.production = self.production_to_join()
        
        return super().get(request, *args, **kwargs)
    
    def post(self, request, *args, **kwargs):
        '''Handler to receive request at save
        '''
        #Inspect if you are invited and get a performance that you can participate in
        self.production = self.production_to_join()
        
        return super().post(request, *args, **kwargs)

--In the GET and POST handlers, based on the information of ʻURLConf, which performance to participate in is saved as an attribute. --The production_to_join () method to get the object of the performance (Production`) of the participation destination is as follows.

views.py


    def production_to_join(self):
        '''Inspect if you are invited and return a performance that you can attend
        '''
        #Throw a 404 error if not invited
        invt_id = self.kwargs['invt_id'];
        invts = Invitation.objects.filter(id=invt_id)
        if len(invts) < 1:
            raise Http404
        invt = invts[0]
        
        #Throw a 404 error if the invitation has expired
        now = datetime.now(timezone.utc)
        if now > invt.exp_dt:
            raise Http404
        
        #PermissionDenied if the accessing user is not invitee
        user = self.request.user
        if user != invt.invitee:
            raise PermissionDenied
        
        return invt.production

-At ["Mechanism to display received invitations"](#Mechanism to display received invitations), the parameter ʻinvt_id is embedded in the link of the "Join" button, so invite using this. I'm getting an object of state (ʻInvitation). --The path to get here (view to participate in the performance) is ʻurls.py` as follows. (> Source).

```path('prod_join/<int:invt_id>/', views.ProdJoin.as_view(), name='prod_join'),```

――It's better to display a message about the expiration date, but here it just returns 404.

views.py


    def form_valid(self, form):
        '''When passed validation
        '''
        #Inspect 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]
        
        #Get the record to save
        new_prod_user = form.save(commit=False)
        
        #Is the correct performance set?
        if new_prod_user.production != invt.production:
            return self.form_invalid(form)
        
        #Is the correct user set?
        if new_prod_user.user != invt.invitee:
            return self.form_invalid(form)
        
        #Delete invitation
        invt.delete()
        
        messages.success(self.request, str(invt.production) + "I participated in.")
        return super().form_valid(form)
    
    def form_invalid(self, form):
        '''When you fail to participate
        '''
        messages.warning(self.request, "I couldn't participate.")
        return super().form_invalid(form)

--After receiving the correct field values (production and ʻuser in this case) from Form, View's form_valid () method will be called, so match it with the contents of the "invitation" again. --The reason this check is needed is that the field value has once left the ʻInvitation object and is exposed to HTML. --If you call the super method after passing the check, a "performance user" will be created, but before that, delete the "invitation".

Create Template

It's a simple page with only a "Join" button and a "Later" button.

production_join.html


{% extends 'base.html' %}

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

<div>&nbsp;</div>

<p>
{{ view.production }}Have been invited to. Are you sure that you want to participate?
</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="later">
</form>
{% endblock %}

--The field value you want to pass to the processing after the POST handler is hidden and written directly. --If you want to write a field value of type ForeignKey directly, use the object's ʻid`.

Summary

We introduced the implementation of the function to invite users to a "project unit" (group) called "performance" and to let the invited users participate in it. The techniques used are summarized below.

--I used settings.AUTH_USER_MODEL to specify the ʻUser type as a model field, and get_user_model () to refer to the ʻUser model in the View method. --Actually, it seems that get_user_model () can be used even when specifying it as a model field. --Even if you set all field values (except ʻid) in code in the View that creates the object, you could use CreateView by emptying fields. --If you don't want the Template to use a selective UI, you can now enter the value of a field (such as ʻusername) to find the object and find it in the code (post () method). .. --In Python, you can add an object's attributes at any time, so keep them as View attributes only when a valid value is found, and later check for the existence of the attributes (using the hasattr () method). I was able to validate. --If you set the data you want to display to the View attribute, you can check the existence of the attribute (whether it is empty or not) on the Tamplate side and display it as needed. --From Template, you could also call the View method and use the return value. --If you want to jump to the same view from multiple pages and decide the return destination dynamically, you could do it by adding a parameter such as from to the URL to that view. --The kwargs received by theget ()andpost ()methods could later be referenced as self.kwargs. --Since it is an attribute of View, it can be referred as view.kwargs.Parameter name from Template. --If you know the field value of the object you are trying to create at the time of GET, you could also write it as hidden in the Template. --If you wrote a field value of type ForeignKey directly into the Template (as hidden, etc.), you could use ʻid for that object and it would be handled correctly by Form`.

Recommended Posts

Divide your data into project-like units with Django (3)
Divide your data into project-like units with Django
Divide data into project-like units with Django (2)
Save tweet data with Django
Manage your data with AWS RDS
Divide Japanese (katakana) into syllable units [Python]
Create your first app with Django startproject
Make holiday data into a data frame with pandas
Train Stanford NER Tagger with your own data
Internationalization with django
CRUD with Django
Try to divide twitter data into SPAM and HAM
Single sign-on to your Django application with AWS SSO
Divide the dataset (ndarray) into arbitrary proportions with NumPy