Untitled Document-11
Untitled Document-11
class UserList(generics.ListAPIView):
"""
GET request
returns all users
"""
queryset = User.objects.all().distinct()
serializer_class = UserSerializer
class UserProfileDetails(generics.RetrieveAPIView):
"""
GET request
receives user as parameter
returns user profile data
"""
lookup_field = 'user__username'
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
class CreateUserProfile(generics.CreateAPIView):
"""
Post request
Creates new user profile
"""
serializer_class = UserProfileSerializer
class UserPostsList(generics.ListAPIView):
"""
GET request
returns all posts from an user
"""
queryset = Post.objects.all().distinct()
serializer_class = PostSerializer
"""
Filter the list of posts of a given author
"""
def filter_queryset(self, queryset):
return queryset.filter(author__username=self.kwargs.get('user__username'))
class UserFriendsDetails(generics.RetrieveAPIView):
"""
GET request
receives user as parameter
returns user's friends list
"""
lookup_field = 'user__username'
queryset = UserProfile.objects.all()
serializer_class = FriendSerializer
class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api'
import factory
from django.contrib.auth.models import User
from socialnetwork.models import Post, UserProfile
from django.utils.crypto import get_random_string
class UserFactory(factory.django.DjangoModelFactory):
"""Creates User test fixture"""
username = get_random_string(length=6)
first_name = get_random_string(length=6)
last_name = get_random_string(length=6)
class Meta:
model = User
class UserProfileFactory(factory.django.DjangoModelFactory):
"""Creates UserProfile test fixture"""
user = factory.SubFactory(UserFactory)
birthdate = '2010-12-12'
class Meta:
model = UserProfile
class PostFactory(factory.django.DjangoModelFactory):
"""Creates Post test fixture"""
text = get_random_string(length=10)
class Meta:
model = Post
from django.db import models
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'date_joined')
class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer(required=True)
friends = UserSerializer(many=True, read_only=True)
class Meta:
model = UserProfile
fields = ('user', 'birthdate', 'profile_picture', 'friends')
# create user
user = User.objects.create(
username = validated_data['user']['username'],
first_name = validated_data['user']['first_name'],
last_name = validated_data['user']['last_name'],
email = validated_data['user']['email'],
)
# create profile
profile = UserProfile.objects.create(
user = user,
birthdate = validated_data['birthdate'],
profile_picture = validated_data['profile_picture'],
)
return profile
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('text', 'image', 'created_date')
class FriendSerializer(serializers.ModelSerializer):
class GetUserTest(APITestCase):
"""
Test module for /profile/<username> GET request
"""
user1 = None
good_url = ''
bad_url = ''
def setUp(self):
# Create user
self.user1 = UserProfileFactory.create()
# Set urls
self.good_url = reverse('api:profile_api', kwargs={'user__username':
self.user1.user.username})
def tearDown(self):
def test_userprofile_return_success(self):
"""
Ensure we get an 200 OK status code when making a valid GET request to
profile/<str:user__username>.
"""
response = self.client.get(self.good_url, format='json')
response.render()
self.assertEqual(response.status_code, 200)
def test_userprofile_return_correct_data(self):
"""
Ensure we get the birthdate of the requested user
"""
response = self.client.get(self.good_url, format='json')
response.render()
data = json.loads(response.content)
self.assertIn(data['birthdate'], self.user1.birthdate)
class GetAllUsersTest(APITestCase):
"""
Test module for /profiles GET request
"""
user1 = None
user2 = None
good_url = ''
bad_url = ''
def setUp(self):
# Create user
self.userprofile1 = UserProfileFactory.create()
self.user2 = UserFactory.create(username='test2', first_name='test2',
last_name='test2')
self.userprofile2 = UserProfileFactory.create(user=self.user2,
birthdate='2010-10-10')
# Set urls
self.good_url = reverse('api:profile_list_api')
def tearDown(self):
def test_userprofiles_return_success(self):
"""
Ensure we get an 200 OK status code when making a valid GET request to
/profiles
"""
response = self.client.get(self.good_url, format='json')
response.render()
self.assertEqual(response.status_code, 200)
def test_userprofiles_return_correct_data(self):
"""
Ensure we get the correct number of users
"""
response = self.client.get(self.good_url, format='json')
response.render()
data = json.loads(response.content)
self.assertEqual(len(data), 2)
app_name = 'api'
urlpatterns = [
path('profiles/', api.UserList.as_view(), name='profile_list_api'),
path('profile/<str:user__username>/', api.UserProfileDetails.as_view(),
name='profile_api'),
path('profile/', api.CreateUserProfile.as_view(), name='create_profile_api'),
path('posts/<str:user__username>/', api.UserPostsList.as_view(), name='posts_api'),
path('friends/<str:user__username>/', api.UserFriendsDetails.as_view(),
name='friends_api'),
]
from django.shortcuts import render
{% include "socialnetwork/base.html" %}
{% block content %}
<div class="container">
<h4 class="my-4">Chat list</h4>
<p> Select a chat from your active chat list to see your messages with that friend.
</p>
<div>
{% if has_chats %}
<ul class="list-chat">
{% for chat_obj, chat_user in chat_list %}
<a href="{% url 'chat:chat_room' chat_user %}"
class="text-decoration-none text-reset">
<li class="list-chat-item d-flex justify-content-between my-3
border p-3" style="width: 60%;">
<div class="d-flex">
<div class="mx-3">
<h5 class="card-title"> {{ chat_user }} </h5>
<span>{{ chat_user.first_name }} {{ chat_user.last_name
}}</span>
</div>
</div>
<div>
<span class="text-left">{{ chat_obj.updated_at }}</span>
</div>
</li>
</a>
{% endfor %}
</ul>
{% else %}
<span>Looks like you haven't started a conversation. Start one.</span>
{% endif %}
<div class="my-3">
<span>Start a new conversation: </span>
<a class="btn btn-primary" href="{% url 'friend:friend_list' user.username
%}"> Go to friends list </a>
</div>
</div>
</div>
{% endblock %}
{% include "socialnetwork/base.html" %}
{% load tz %}
{% block content %}
<div class="container my-3 p-3" style="height: 80vh; width: 70vw; max-width:
500px;">
<h3>Chat between: {{user.username}} and {{friend.username}}</h3>
<div id="message-container" class="overflow-auto d-flex flex-md-column"
style="height: 80%;">
<div id="message-list">
{% for message in messages %}
<div class="text-start m-2 d-flex flex-column
justify-content-start">
<div>
<span class="fw-bold">{{message.sender.username}}</span>
<span class="chat-message-date">{{message.timestamp|localtime}}</span>
</div>
<div>
<span>{{message.content}}</span>
</div>
</div>
{% endfor %}
</div>
</div>
<form id="message-form">
{% csrf_token %}
<textarea class="form-control"name="message" id="message" rows="3"
required></textarea>
<input type="submit" value="Send" class="btn btn-primary my-3">
</form>
</div>
{% endblock content %}
{% block custom_javascript %}
<script>
chatSocket.onopen = function(event) {
console.log('Connection is open')
}
chatSocket.onmessage = function(event) {
console.log('Message received', event)
// set date
const currentdate = new Date().toDateString()
msg_date_span.append(document.createTextNode(
currentdate
))
// set content
msg_content_span.append(document.createTextNode(
data.text
))
msg_content_div.append(msg_content_span)
msg_container.append(msg_content_div)
msg_list.append(msg_container)
chatSocket.onclose = function(event) {
console.log('Connection closed', event)
}
chatSocket.onerror = function(event) {
console.log('Something went wrong', event)
}
function sendMessage(event){
if (event.preventDefault) {
event.preventDefault()
}
chatSocket.send(document.getElementById('message').value)
messageForm.reset()
return false
}
</script>
{% endblock custom_javascript %}
from django.contrib import admin
from chat.models import Chat, ChatMessage
# Register your models here.
class MessageInline(admin.StackedInline):
model = ChatMessage
fields = ('sender', 'content')
readonly_fields = ('sender', 'content')
class ChatAdmin(admin.ModelAdmin):
model = Chat
inlines = (MessageInline,)
admin.site.register(Chat, ChatAdmin)
from django.apps import AppConfig
class ChatConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'chat'
class ChatConsumer(AsyncConsumer):
print(f'[{self.channel_name}] - Connected')
msg = json.dumps({
'text': event.get('text'),
'username': self.scope['user'].username
})
await self.save_message(event.get('text'))
# send to room
await self.channel_layer.group_send(
self.room_name,
{
'type': 'websocket.message',
'text': msg
}
)
@database_sync_to_async
def save_message(self, content):
"""
Save chat message into the database
"""
# store message in db
ChatMessage.objects.create(
chat = self.chat_obj,
sender = self.scope['user'],
content = content
)
chat = Chat.objects.get(id=self.chat_obj.id)
chat.updated_at = datetime.datetime.now()
chat.save()
class ChatManager(models.Manager):
class Chat(models.Model):
@property
def room_name(self):
"Returns chat room id"
return f'{self.id}'
class ChatMessageManager(models.Manager):
class ChatMessage(models.Model):
objects = ChatMessageManager()
app_name = 'chat'
urlpatterns = [
path('chat_list', ChatList.as_view(), name='chat_list'),
path('chat_room/<str:username>/', ChatView.as_view(), name='chat_room')
]
class ChatView(View):
template_name = 'chat/chat.html'
def get_object(self):
other_username = self.kwargs.get("username")
self.other_user = get_user_model().objects.get(username=other_username)
obj = Chat.objects.get_or_create_chat(self.request.user, self.other_user)
if obj == None:
raise Http404
return obj
context = {}
context['user'] = self.request.user
context['chat'] = self.get_object()
context['friend'] = self.other_user
context['messages'] = self.get_object().chatmessage_set.all()
class ChatList(View):
template_name = 'chat/chat_list.html'
context = {}
chat_list = Chat.objects.by_user(self.request.user)
chat_user_list = []
context['has_chats'] = has_chats
Friend_list.html
{% include "socialnetwork/base.html" %}
{% block content %}
<div class="container">
<h4 class="my-4">Friends list</h4>
<div class="row row-cols-1 row-cols-md-6 g-6 my-3">
{% for friend in friends %}
<div class="card py-3">
<img src="{{friend.userprofile.profile_picture.url}}" class="rounded
mx-auto d-block" width="50" height="50" />
<div class="card-body text-center">
<h5 class="card-title text-center">
<a href="{% url 'profile' friend.username %}"
style="text-decoration: none">
{{friend.username}}
</a>
</h5>
<span>{{friend.first_name}} {{friend.last_name}}</span>
{% if is_logged_user_profile %}
<a href="{% url 'chat:chat_room' friend.username %}" class="btn
btn-primary my-2">Chat</a>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
Friend_request_functions.html
<script src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous"></script>
<script type="text/javascript">
data = {
"csrfmiddlewaretoken": "{{csrf_token}}",
"receiver_username": username,
}
$.ajax({
type: "POST",
dataType: "json",
url: "{% url 'friend:send_friend_request' %}",
timeout: 3000,
data: data,
success: function(data){
console.log('success: ' + data.response)
},
error: function(data){
console.log('error: ' + data.response)
},
complete: function(data){
console.log('complete: ' + data.response)
updatedView()
},
})
}
data = {
"csrfmiddlewaretoken": "{{csrf_token}}",
"request_id": request_id,
}
$.ajax({
type: "POST",
dataType: "json",
url: "{% url 'friend:accept_friend_request' %}",
timeout: 3000,
data: data,
success: function(data){
console.log('success: ' + data.response)
},
error: function(data){
console.log('error: ' + data.response)
},
complete: function(data){
console.log('complete: ' + data.response)
updatedView()
},
})
}
data = {
"csrfmiddlewaretoken": "{{csrf_token}}",
"request_id": request_id,
}
$.ajax({
type: "POST",
dataType: "json",
url: "{% url 'friend:decline_friend_request' %}",
timeout: 3000,
data: data,
success: function(data){
console.log('success: ' + data.response)
},
error: function(data){
console.log('error: ' + data.response)
},
complete: function(data){
console.log('complete: ' + data.response)
updatedView()
},
})
}
data = {
"csrfmiddlewaretoken": "{{csrf_token}}",
"request_id": request_id,
}
$.ajax({
type: "POST",
dataType: "json",
url: "{% url 'friend:cancel_friend_request' %}",
timeout: 3000,
data: data,
success: function(data){
console.log('success: ' + data.response)
},
error: function(data){
console.log('error: ' + data.response)
},
complete: function(data){
console.log('complete: ' + data.response)
updatedView()
},
})
}
</script>
Friend_request_list.html
{% include "socialnetwork/base.html" %}
{% block content %}
<script type="text/javascript">
function reloadPage(){
location.reload();
}
</script>
{% endblock %}
Admin.py
from django.contrib import admin
admin.site.register(FriendRequest)
Apps.py
from django.apps import AppConfig
class FriendConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'friend'
Helpers.py
from friend.models import FriendRequest
Models.py
from django.db import models
from django.conf import settings
from django.utils import timezone
from django.contrib.auth.models import User
from socialnetwork.models import UserProfile
def accept_request(self):
"""
Add both users to each other's friends list
"""
sender = self.sender
receiver = self.receiver
def deactivate_request(self):
"""
Cancel or decline friend request
"""
self.is_active = False
self.save()
def activate_request(self):
"""
Activate friend request. Shows up again
"""
self.is_active = True
self.save()
Tests.py
from django.test import TestCase
Urls.py
from django.urls import path
from . import views
from django.contrib.auth.decorators import login_required
app_name = 'friend'
urlpatterns = [
path('send_friend_request/', views.send_friend_request, name =
'send_friend_request'),
path('accept_friend_request/', views.accept_friend_request, name =
'accept_friend_request'),
path('decline_friend_request/', views.decline_friend_request, name =
'decline_friend_request'),
path('cancel_friend_request/', views.cancel_friend_request, name =
'cancel_friend_request'),
path('friend_request_list/', views.friend_request_list, name =
'friend_request_list'),
path('friend_list/<str:username>', views.friend_list, name='friend_list'),
]
Views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.contrib import messages
from django.http import JsonResponse
from django.views.generic.base import View
@login_required
def send_friend_request(request, *args, **kwargs):
"""
Creates a new friend request with the logged user as sender and provided user as
received
"""
user = request.user
data = {}
if receiver_username:
receiver = User.objects.get(username=receiver_username)
# get user friends list
user_friends = user.userprofile.friends.all()
is_friend = user_friends.filter(username=receiver.username)
# not friend
if not is_friend:
return JsonResponse(data)
@login_required
def accept_friend_request(request, *args, **kwargs):
"""
Both users are added to each other's friendlist. Request is then set as not active
"""
user = request.user
data = {}
if request_id:
# get the request
friend_request = FriendRequest.objects.get(id=request_id)
# accept if logged user is the receiver of the request
if friend_request.receiver == user:
friend_request.accept_request()
data["response"] = "Friend request accepted"
# if logged user is not the receiver then its the wrong request
else:
data["response"] = "Error. not your request"
else:
data['response'] = "No request"
else:
data['response'] = "User not authenticated"
return JsonResponse(data)
@login_required
def decline_friend_request(request, *args, **kwargs):
"""
Friend request object is set to inactive
"""
user = request.user
data = {}
if request_id:
# get the request
friend_request = FriendRequest.objects.get(id=request_id)
# decline if logged user is the receiver of the request
if friend_request.receiver == user:
friend_request.deactivate_request()
data["response"] = "Friend request declined"
else:
data["response"] = "Error. not your request"
else:
data['response'] = "No request"
else:
data['response'] = "User not authenticated"
return JsonResponse(data)
@login_required
def cancel_friend_request(request, *args, **kwargs):
"""
Friend request object is set to inactive
"""
user = request.user
data = {}
if request.method == "POST" and user.is_authenticated:
request_id = request.POST.get("request_id")
if request_id:
# get the request
friend_request = FriendRequest.objects.get(id=request_id)
# inactivate request if logged user sent it and the request is still active
if friend_request.sender == user and friend_request.is_active:
friend_request.deactivate_request()
data["response"] = "Friend request cancelled"
else:
data["response"] = "Error. not your request"
else:
data['response'] = "No request"
else:
data['response'] = "User not authenticated"
return JsonResponse(data)
@login_required
def friend_request_list(request, *args, **kwargs):
"""
Returns list of friend requests sent and received of the logged user
"""
user = request.user
context = {}
if user.is_authenticated:
else:
messages.error(request, 'User no authenticated')
@login_required
def friend_list(request, username, *args, **kwargs):
"""
Returns list of friends of given username
"""
user = request.user
if user.is_authenticated:
user_profile = User.objects.get(username=username)
if user_profile:
# get friends
friends = user_profile.userprofile.friends.all()
is_logged_user_profile = True if user.username == username else False
context = {
'friends': friends,
'is_logged_user_profile': is_logged_user_profile
}
else:
messages.error(request, 'User no authenticated')
Templates
Base.html
{% load static %}
<!doctype html>
<html lang="en">
{% include "./header.html" %}
<body>
{% include "./navbar.html" %}
<div class="container-fluid">
{% block content %}
{% endblock content %}
</div>
{% include "./scripts.html" %}
</body>
</html>
Header.html
{% load static %}
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Social Network</title>
</head>
Home.html
{% extends "./base.html" %}
{% block content %}
<div class="container my-3">
{% if user.is_authenticated %}
<p class="text-center py-3">Welcome, {{ user.username }}. Thanks for logging
in.</p>
{% if messages %}
<ul class="messages">
{% for message in messages %}
<div {% if message.tags %}
{% if message.tags == "error" %}
class="alert alert-danger"
{% endif %}
class="alert alert-{{ message.tags }}"
{% endif %}
role="alert">
{{ message }}
</div>
{% endfor %}
</ul>
{% endif %}
{% csrf_token %}
<div>
<div>
<label class="form-label" for="text"></label>
{{ post_form.text }}
</div>
<p class="text-danger"><small>{{ post_form.text.errors.0
}}</small></p>
</div>
</form>
</div>
{% else %}
<p>Welcome, new user. Please log in.</p>
{% endif %}
</div>
{% endblock %}
Login.html
{% load static %}
<!doctype html>
<html lang="en">
{% include "./header.html" %}
<body>
<div class="vh-100" >
<div class="container h-100">
<div class="row d-flex justify-content-center align-items-center
h-100">
<div class="col-md-10 col-lg-6">
<div class="card text-black" style="border-radius: 25px;">
<div class="card-body p-md-5">
<div class="row justify-content-center">
{% if messages %}
<ul class="messages">
{% for message in messages %}
<div {% if message.tags %}
{% if message.tags == "error" %}
class="alert alert-danger"
{% endif %}
class="alert alert-{{ message.tags }}"
{% endif %}
role="alert">
{{ message }}
</div>
{% endfor %}
</ul>
{% endif %}
<p class="text-center h1 fw-bold mb-5 mx-1 mx-md-4
mt-4">Login</p>
{% csrf_token %}
</form>
<div class="mt-4">
<div class="d-flex flex-column justify-content-center
align-items-center">
<p class="m-0">Create an account</p>
<a href="{% url 'signup' %}">Sign Up</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% include "./scripts.html" %}
</body>
</html>
Navbar.html
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="{% url 'home' %}">Home</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse d-flex justify-content-between"
id="navbarSupportedContent">
<form class="d-flex" method="get" action="{% url 'user-search' %}">
<input class="form-control me-2" type="search" placeholder="Search
users" aria-label="Search" name="query" value={{request.GET.query}}>
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
<div class="navbar-nav d-flex align-items-center">
<div>
<a class="nav-item nav-link" href="{% url 'chat:chat_list' %}">
Messages
</a>
</div>
<div>
<a class="nav-link d-flex align-items-center" href="{% url
'profile' request.user.username %}">
<img src="{{ request.user.userprofile.profile_picture.url }}"
class="rounded" width="35" height="35" />
<p class="mx-2 my-0"> {{ request.user.username }} </p>
</a>
</div>
<div class="nav-item active">
<a class="nav-link" href="{% url 'logout' %}"> Logout </a>
</div>
</div>
</div>
</div>
</nav>
Post_list.html
{% include "./base.html" %}
{% block content %}
<h4>Home</h4>
{% csrf_token %}
{{ post_form }}
</form>
{% endblock %}
Profile_edit.html
{% include "./base.html" %}
{% block content %}
<div class="col-md-9">
<div class="card card-body p-4">
{% csrf_token %}
</form>
</div>
</div>
</div>
</div>
{% endblock %}
Profile.html
{% include "./base.html" %}
{% block content %}
{% if messages %}
<ul class="messages">
{% for message in messages %}
<div {% if message.tags %}
{% if message.tags == "error" %}
class="alert alert-danger"
{% endif %}
class="alert alert-{{ message.tags }}"
{% endif %}
role="alert">
{{ message }}
</div>
{% endfor %}
</ul>
{% endif %}
{% if profile.user.first_name %}
<h3>Name: {{ profile.user.first_name }} {{ profile.user.last_name
}}</h3>
{% endif %}
{% if profile.birthdate %}
<p>Birthdate: {{ profile.birthdate }} </p>
{% endif %}
{% if is_logged_user %}
<a class="btn btn-warning" href="{% url 'profile-edit' %}"> Edit
Profile</a>
{% endif %}
{% if is_friend %}
<span class="badge bg-primary">Friend</span>
{% endif %}
<!-- if not own profile and not a friend, then show request status -->
{% if not is_friend and not is_logged_user %}
{% if sent_request == 'no_request' %}
<button class="btn btn-primary btn-sm"
onclick='sendFriendRequest("{{profile.user.username}}", reloadPage)'> Send Friend
Request</button>
{% endif %}
{% if sent_request == 'sent_to_user' %}
<p>Pending friend request:</p>
<div class="d-flex">
<button class="btn btn-primary btn-sm"
onclick='acceptFriendRequest("{{pending_to_user_id}}", reloadPage)'> Accept </button>
<button class="btn btn-danger btn-sm mx-3"
onclick='declineFriendRequest("{{pending_to_user_id}}", reloadPage)'> Decline
</button>
</div>
{% endif %}
{% if sent_request == 'sent_to_them' %}
<div class="d-flex">
<button class="btn btn-danger btn-sm"
onclick='cancelFriendRequest("{{pending_to_them_id}}", reloadPage)'> Cancel Friend
Request</button>
</div>
{% endif %}
{% endif %}
</div>
</div>
<div class="d-flex flex-column justify-content-center">
<a class="my-3">
<div>
<a class="btn btn-primary" href="{% url 'friend:friend_list'
profile.user.username %}">Friends ({{ friends|length }})</a>
</div>
</a class="my-3">
{% if is_logged_user %}
<a>
<div>
<a class="btn btn-warning" href="{% url
'friend:friend_request_list' %}">Friend requests ({{ friend_requests_received|length
}})</a>
</div>
</a>
{% endif %}
</div>
</div>
<h3 class="my-3">Activity</h3>
{% if post_list %}
{% for post in post_list %}
{% endfor %}
{% else %}
<p>No posts to show</p>
{% endif %}
</div>
</div>
{% include 'friend/friend_request_functions.html' %}
<script type="text/javascript">
function reloadPage(){
location.reload();
}
</script>
{% endblock %}
Scripts.html*************
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous"></script>
Signup.html
{% load static %}
<!doctype html>
<html lang="en">
{% include "./header.html" %}
<body>
<div class="vh-100" >
<div class="container h-100">
<div class="row d-flex justify-content-center align-items-center
h-100">
<div class="col-lg-10">
<div class="card text-black" style="border-radius: 25px;">
<div class="card-body p-md-5">
<div class="row justify-content-center">
{% csrf_token %}
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% include "./scripts.html" %}
</body>
</html>
User_seaech.html
{% include "./base.html" %}
{% block content %}
<div class="container">
{% if users %}
<ul class="list-group">
{% for user in users %}
<li class="list-group-item d-flex justify-content-between my-3"
style="width: 60%;">
<div class="d-flex">
{% if user.userprofile.profile_picture %}
<img src="{{user.userprofile.profile_picture.url}}" class="rounded"
width="50" height="50" />
{% endif %}
<div class="mx-3">
<h5 class="card-title">
<a href="{% url 'profile' user.username %}"
style="text-decoration: none">{{user.username}}</a>
</h5>
<span>{{user.first_name}} {{user.last_name}}</span>
</div>
</div>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endblock %}
Admin.py
from django.contrib import admin
from .models import *
class UserProfileAdmin(admin.ModelAdmin):
list_filter = ['user']
list_display = ['user']
search_fields = ['user']
class Meta:
model = UserProfile
admin.site.register(UserProfile, UserProfileAdmin)
admin.site.register(Post)
Apps.py
from django.apps import AppConfig
class SocialnetworkConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'socialnetwork'
Forms.py
from django import forms
from django.forms import ModelForm
from .models import *
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
class UserSignupForm(UserCreationForm):
class Meta:
model = User
fields = ('first_name', 'last_name', 'username', 'email', 'password1',
'password2')
widgets = {
'first_name': forms.TextInput(attrs={'class': 'form-control'}),
'last_name': forms.TextInput(attrs={'class': 'form-control'}),
'username': forms.TextInput(attrs={'class': 'form-control'}),
'email': forms.EmailInput(attrs={'class': 'form-control'}),
'password1': forms.PasswordInput(attrs={'class': 'form-control'}),
'password2': forms.PasswordInput(attrs={'class': 'form-control'}),
}
class UserProfileForm(forms.ModelForm):
birthdate = forms.DateField(widget=forms.DateInput(format=('%Y-%m-%d'),
attrs={'type': 'date', 'class': 'form-control'}))
profile_picture = forms.ImageField(required=False, error_messages = {'invalid':
"Image files only"}, widget=forms.FileInput)
class Meta:
model = UserProfile
fields = ('birthdate', 'profile_picture')
class UserForm(forms.ModelForm):
first_name = forms.CharField(max_length=255, widget=forms.TextInput(attrs={'class':
'form-control'}))
last_name = forms.CharField(max_length=255, widget=forms.TextInput(attrs={'class':
'form-control'}))
class Meta:
model = User
fields = ('first_name', 'last_name', 'email')
widgets = {
'first_name': forms.TextInput(attrs={'class': 'form-control'}),
'last_name': forms.TextInput(attrs={'class': 'form-control'}),
'email': forms.EmailInput(attrs={'class': 'form-control'}),
}
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['text', 'image']
Models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from datetime import date
from django.core.validators import MaxValueValidator
class UserProfile(models.Model):
"""
Profile of a user, includes additional fields such as birthdate, picture
"""
def __str__(self):
return self.user.username
class Post(models.Model):
"""
Post includes author, text content, image and date it was created
"""
text = models.TextField()
image = models.ImageField(upload_to='uploads/posts_pictures', null=True)
created_date = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
Tests.py
from socialnetwork.models import UserProfile
from django.test import TestCase, Client
from django.urls import reverse
from django.contrib.auth.models import User
import json
class LoginTestCase(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user('test', '[email protected]', 'mypassword')
def test_login(self):
response = self.client.post(reverse('login'), data={
'username': 'test',
'password': 'mypassword',
})
# test its redirected to home after login
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, '/home')
class SignupTestCase(TestCase):
def setUp(self):
self.client = Client()
def test_signup(self):
response = self.client.post(reverse('signup'), data={
'username': 'testuseragain',
'email': '[email protected]',
'first_name': 'test 1',
'last_name': 'test 1',
'birthdate': '2010-12-12',
'password1': 'stronglongpwd',
'password2': 'stronglongpwd'
})
self.assertEqual(response.status_code, 302)
user = User.objects.get(username='testuseragain')
self.assertEqual(user.email, '[email protected]')
class UserSearchTestCase(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user('test', '[email protected]', 'mypassword')
self.searched_user = User.objects.create_user('searched_test', '[email protected]',
'smypassword')
def test_search_user(self):
# login
self.client.post(reverse('login'), data={
'username': 'test',
'password': 'mypassword',
})
class UserProfileTestCase(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user('test', '[email protected]', 'mypassword')
self.userprofile = UserProfile.objects.create(user=self.user,
birthdate='2010-12-12')
def test_profile_user(self):
# login
self.client.post(reverse('login'), data={
'username': 'test',
'password': 'mypassword',
})
# returns profile
response = self.client.get(reverse('profile', kwargs={'username': 'test'}))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'socialnetwork/profile.html')
Urls.py
from django import urls
from django.urls import include, path
from . import views
from django.contrib.auth.decorators import login_required
urlpatterns = [
path('', login_required(login_url='/login')(views.Home.as_view()), name = 'home'),
path('home/', login_required(login_url='/login')(views.Home.as_view()), name =
'home'),
path('signup/', views.user_signup, name = 'signup'),
path('login/', views.user_login, name='login'),
path('logout/', views.user_logout, name='logout'),
path('profile/<str:username>', views.UserProfileView.as_view(), name='profile'),
path('profile/edit/', views.EditProfile.as_view(), name='profile-edit'),
path('search/', views.UserSearch.as_view(), name='user-search'),
]
Views.py
from django.contrib import auth
from django.shortcuts import get_object_or_404, redirect, render
from django.views import View
from .models import *
from .forms import *
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.contrib.auth.forms import AuthenticationForm
from django.urls import reverse_lazy
from friend.helpers import find_friend_request
from friend.models import FriendRequest
from django.db.models.query_utils import Q
from django.db.models.functions import Concat
from django.db.models import Value as V
@login_required
def user_logout(request):
logout(request)
return redirect('/login')
def user_login(request):
"""
Authenticates user
"""
if request.user.is_authenticated:
return redirect('/home')
else:
if request.method == 'POST':
return redirect('/home')
else:
messages.error(request, 'Your account is disabled.')
else:
messages.error(request, 'Invalid login details supplied.')
def user_signup(request):
"""
Creates new User instance and UserProfile instance
"""
if request.user.is_authenticated:
return redirect('/home')
else:
registered = False
if request.method == 'POST':
user_form = UserSignupForm(data=request.POST)
profile_form = UserProfileForm(data=request.POST)
if 'birthdate' in user_form.cleaned_data:
profile.birthdate = request.DATA['birthdate']
profile.save()
registered = True
messages.success(request, 'Account created.')
return redirect('/login')
else:
print(user_form.errors, profile_form.errors)
else:
user_form = UserSignupForm()
profile_form = UserProfileForm()
context = {
'user_form': user_form,
'profile_form': profile_form,
'registered': registered
}
class Home(View):
"""
Displays the homepage. News feed of the logged user
"""
user = request.user
profile = UserProfile.objects.filter(user=user).first()
post_form = PostForm()
context = {
'post_list': feed_posts,
'post_form': post_form
}
else:
messages.error(request, 'Create an account.')
logout(request)
return redirect('/login')
new_post.save()
user_friends = user.userprofile.friends.all()
feed_posts = Post.objects.filter(Q(author__in=user_friends) |
Q(author=user)).order_by('-created_date')
new_post_form = PostForm()
context = {
'post_list': feed_posts,
'post_form': new_post_form
}
class UserProfileView(View):
"""
Returns info of the user profile such as posts, friends
"""
context = {}
profile = UserProfile.objects.get(user__username=username)
profile_user = profile.user
profile_posts =
Post.objects.filter(author=profile_user).order_by('-created_date')
profile_friends = profile.friends.all()
user = request.user
is_logged_user = False
is_friend = False
if user.is_authenticated and user != profile_user:
is_logged_user = False
if profile_friends.filter(username=user.username):
is_friend = True
else:
is_friend = False
# if not friends, check if there is an active friend request
# logged user has pending request
if find_friend_request(user_sender=profile_user, user_receiver=user) !=
False:
friend_request_sent_to_user =
find_friend_request(user_sender=profile_user, user_receiver=user)
pending_to_user_id = friend_request_sent_to_user.id
context['pending_to_user_id'] = pending_to_user_id
context['sent_request'] = 'sent_to_user'
# the other user has a pending request
elif find_friend_request(user_sender=user, user_receiver=profile_user)
!= False:
friend_request_sent_by_user = find_friend_request(user_sender=user,
user_receiver=profile_user)
pending_to_them_id = friend_request_sent_by_user.id
context['pending_to_them_id'] = pending_to_them_id
context['sent_request'] = 'sent_to_them'
# there are no requests
else:
context['sent_request'] = 'no_request'
context['user'] = user
context['profile'] = profile
context['post_list'] = profile_posts
context['is_logged_user'] = is_logged_user
context['is_friend'] = is_friend
context['friends'] = profile_friends
class EditProfile(View):
def get(self, request, *args, **kwargs):
user = request.user
profile = get_object_or_404(UserProfile, user=user)
user = request.user
profile = get_object_or_404(UserProfile, user=user)
return redirect('/profile/'+user.username)
else:
print(user_form.errors, profile_form.errors)
context = {
'user_form': user_form,
'profile_form': profile_form,
}
class UserSearch(View):
context = {
'users': users,
}
Asgi.py
"""
ASGI config for socialnetwork_project project.
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'socialnetwork_project.settings')
application = get_asgi_application()
Routing.py
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from django.urls import path
from chat.consumers import ChatConsumer
application = ProtocolTypeRouter({
'websocket':AuthMiddlewareStack(
URLRouter([
path('ws/chat/chat_room/<str:username>/', ChatConsumer.as_asgi())
])
)
})
Settings.py
"""
Django settings for socialnetwork_project project.
ALLOWED_HOSTS = [
'localhost',
'127.0.0.1'
]
# Authentication backends
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # default
# any other authentication backends
)
# Application definition
INSTALLED_APPS = [
'socialnetwork.apps.SocialnetworkConfig',
'api.apps.ApiConfig',
'friend.apps.FriendConfig',
'chat.apps.ChatConfig',
'channels',
'rest_framework',
'django_bootstrap5',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'socialnetwork_project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
ASGI_APPLICATION = 'socialnetwork_project.routing.application'
WSGI_APPLICATION = 'socialnetwork_project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': BASE_DIR / 'db.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME':
'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
USE_I18N = True
USE_L10N = True
USE_TZ = True
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
SITE_ID = 1
LOGIN_URL = '/login'
LOGIN_REDIRECT_URL = '/home'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
# CHANNEL_LAYERS = {
# "default": {
# "BACKEND": "channels_redis.core.RedisChannelLayer",
# "CONFIG": {
# "hosts": [("127.0.0.1", 6379)],
# },
# },
# }
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer"
}
}
Urls.py
"""socialnetwork_project URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('', include('socialnetwork.urls')),
path('admin/', admin.site.urls),
path('friend', include('friend.urls', namespace='friend')),
path('chat/', include('chat.urls', namespace='chat')),
path('api/', include('api.urls', namespace='api'))
]
Wsgi.py
"""
WSGI config for socialnetwork_project project.
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'socialnetwork_project.settings')
application = get_wsgi_application()
Styles.css
.chat-message-date {
font-size: small;
}
Manage.py
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'socialnetwork_project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
Pipfile
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
django = "*"
django-bootstrap5 = "*"
django-allauth = "*"
pillow = "*"
channels-redis = "*"
redis = "*"
asgiref = "*"
djangorestframework = "*"
requests = "*"
factory-boy = "*"
[dev-packages]
[requires]
python_version = "3.9"
pipfile.lock
{
"_meta": {
"hash": {
"sha256":
"9071f233c19248277df2234dd604d37e08c38e343052ad89d0a240da2dbee9af"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.9"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"aioredis": {
"hashes": [
"sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a",
"sha256:b61808d7e97b7cd5a92ed574937a079c9387fdadd22bfbfa7ad2fd319ecc26e3"
],
"version": "==1.3.1"
},
"asgiref": {
"hashes": [
"sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9",
"sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"
],
"index": "pypi",
"version": "==3.4.1"
},
"async-timeout": {
"hashes": [
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
],
"markers": "python_full_version >= '3.5.3'",
"version": "==3.0.1"
},
"attrs": {
"hashes": [
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1,
3.2, 3.3, 3.4'",
"version": "==21.2.0"
},
"autobahn": {
"hashes": [
"sha256:9195df8af03b0ff29ccd4b7f5abbde957ee90273465942205f9a1bad6c3f07ac",
"sha256:e126c1f583e872fb59e79d36977cfa1f2d0a8a79f90ae31f406faae7664b8e03"
],
"markers": "python_version >= '3.7'",
"version": "==21.3.1"
},
"automat": {
"hashes": [
"sha256:7979803c74610e11ef0c0d68a2942b152df52da55336e0c9d58daf1831cbdf33",
"sha256:b6feb6455337df834f6c9962d6ccf771515b7d939bca142b29c20c2376bc6111"
],
"version": "==20.2.0"
},
"beautifulsoup4": {
"hashes": [
"sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf",
"sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"
],
"markers": "python_version >= '3.1'",
"version": "==4.10.0"
},
"certifi": {
"hashes": [
"sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
"sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
],
"version": "==2021.5.30"
},
"cffi": {
"hashes": [
"sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d",
"sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771",
"sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872",
"sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c",
"sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc",
"sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762",
"sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202",
"sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5",
"sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548",
"sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a",
"sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f",
"sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20",
"sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218",
"sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c",
"sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e",
"sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56",
"sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224",
"sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a",
"sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2",
"sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a",
"sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819",
"sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346",
"sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b",
"sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e",
"sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534",
"sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb",
"sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0",
"sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156",
"sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd",
"sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87",
"sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc",
"sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195",
"sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33",
"sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f",
"sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d",
"sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd",
"sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728",
"sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7",
"sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca",
"sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99",
"sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf",
"sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e",
"sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c",
"sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5",
"sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"
],
"version": "==1.14.6"
},
"channels": {
"hashes": [
"sha256:0ff0422b4224d10efac76e451575517f155fe7c97d369b5973b116f22eeaf86c",
"sha256:fdd9a94987a23d8d7ebd97498ed8b8cc83163f37e53fc6c85098aba7a3bb8b75"
],
"markers": "python_version >= '3.6'",
"version": "==3.0.4"
},
"channels-redis": {
"hashes": [
"sha256:0a18ce279c15ba79b7985bb12b2d6dd0ac8a14e4ad6952681f4422a4cc4a5ea9",
"sha256:1abd5820ff1ed4ac627f8a219ad389e4c87e52e47a230929a7a474e95dd2c6c2"
],
"index": "pypi",
"version": "==3.3.0"
},
"charset-normalizer": {
"hashes": [
"sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b",
"sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"
],
"markers": "python_version >= '3'",
"version": "==2.0.4"
},
"constantly": {
"hashes": [
"sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35",
"sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d"
],
"version": "==15.1.0"
},
"cryptography": {
"hashes": [
"sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e",
"sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b",
"sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7",
"sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085",
"sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc",
"sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a",
"sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498",
"sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9",
"sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c",
"sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7",
"sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb",
"sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14",
"sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af",
"sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e",
"sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5",
"sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06",
"sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"
],
"version": "==3.4.8"
},
"daphne": {
"hashes": [
"sha256:76ffae916ba3aa66b46996c14fa713e46004788167a4873d647544e750e0e99f",
"sha256:a9af943c79717bc52fe64a3c236ae5d3adccc8b5be19c881b442d2c3db233393"
],
"markers": "python_version >= '3.6'",
"version": "==3.0.2"
},
"defusedxml": {
"hashes": [
"sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69",
"sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1,
3.2, 3.3, 3.4'",
"version": "==0.7.1"
},
"django": {
"hashes": [
"sha256:95b318319d6997bac3595517101ad9cc83fe5672ac498ba48d1a410f47afecd2",
"sha256:e93c93565005b37ddebf2396b4dc4b6913c1838baa82efdfb79acedd5816c240"
],
"index": "pypi",
"version": "==3.2.7"
},
"django-allauth": {
"hashes": [
"sha256:6d46be0e1480316ccd45476db3aefb39db70e038d2a543112d314b76bb999a4e"
],
"index": "pypi",
"version": "==0.45.0"
},
"django-bootstrap5": {
"hashes": [
"sha256:0021355df9212c511876e3f5336ba470416a6dd52f61bf5acec5bb783ab41ff5",
"sha256:ac05ca69b990e62657c0bf40db14946c22fd64c7fb9dfe094a9f22ecc58f86c5"
],
"index": "pypi",
"version": "==2.1.2"
},
"djangorestframework": {
"hashes": [
"sha256:6d1d59f623a5ad0509fe0d6bfe93cbdfe17b8116ebc8eda86d45f6e16e819aaf",
"sha256:f747949a8ddac876e879190df194b925c177cdeb725a099db1460872f7c0a7f2"
],
"index": "pypi",
"version": "==3.12.4"
},
"factory-boy": {
"hashes": [
"sha256:1d3db4b44b8c8c54cdd8b83ae4bdb9aeb121e464400035f1f03ae0e1eade56a4",
"sha256:401cc00ff339a022f84d64a4339503d1689e8263a4478d876e58a3295b155c5b"
],
"index": "pypi",
"version": "==3.2.0"
},
"faker": {
"hashes": [
"sha256:6714c153433086681b26e5c95ee314ee0fcd45ec05f2426097543dd4c70789a6",
"sha256:810859626d19e62a2a13aa4a08d59ada131f0522431eec163b09b6df147a25b9"
],
"markers": "python_version >= '3.6'",
"version": "==8.12.1"
},
"hiredis": {
"hashes": [
"sha256:04026461eae67fdefa1949b7332e488224eac9e8f2b5c58c98b54d29af22093e",
"sha256:04927a4c651a0e9ec11c68e4427d917e44ff101f761cd3b5bc76f86aaa431d27",
"sha256:07bbf9bdcb82239f319b1f09e8ef4bdfaec50ed7d7ea51a56438f39193271163",
"sha256:09004096e953d7ebd508cded79f6b21e05dff5d7361771f59269425108e703bc",
"sha256:0adea425b764a08270820531ec2218d0508f8ae15a448568109ffcae050fee26",
"sha256:0b39ec237459922c6544d071cdcf92cbb5bc6685a30e7c6d985d8a3e3a75326e",
"sha256:0d5109337e1db373a892fdcf78eb145ffb6bbd66bb51989ec36117b9f7f9b579",
"sha256:0f41827028901814c709e744060843c77e78a3aca1e0d6875d2562372fcb405a",
"sha256:11d119507bb54e81f375e638225a2c057dda748f2b1deef05c2b1a5d42686048",
"sha256:1233e303645f468e399ec906b6b48ab7cd8391aae2d08daadbb5cad6ace4bd87",
"sha256:139705ce59d94eef2ceae9fd2ad58710b02aee91e7fa0ccb485665ca0ecbec63",
"sha256:1f03d4dadd595f7a69a75709bc81902673fa31964c75f93af74feac2f134cc54",
"sha256:240ce6dc19835971f38caf94b5738092cb1e641f8150a9ef9251b7825506cb05",
"sha256:294a6697dfa41a8cba4c365dd3715abc54d29a86a40ec6405d677ca853307cfb",
"sha256:3d55e36715ff06cdc0ab62f9591607c4324297b6b6ce5b58cb9928b3defe30ea",
"sha256:3dddf681284fe16d047d3ad37415b2e9ccdc6c8986c8062dbe51ab9a358b50a5",
"sha256:3f5f7e3a4ab824e3de1e1700f05ad76ee465f5f11f5db61c4b297ec29e692b2e",
"sha256:508999bec4422e646b05c95c598b64bdbef1edf0d2b715450a078ba21b385bcc",
"sha256:5d2a48c80cf5a338d58aae3c16872f4d452345e18350143b3bf7216d33ba7b99",
"sha256:5dc7a94bb11096bc4bffd41a3c4f2b958257085c01522aa81140c68b8bf1630a",
"sha256:65d653df249a2f95673976e4e9dd7ce10de61cfc6e64fa7eeaa6891a9559c581",
"sha256:7492af15f71f75ee93d2a618ca53fea8be85e7b625e323315169977fae752426",
"sha256:7f0055f1809b911ab347a25d786deff5e10e9cf083c3c3fd2dd04e8612e8d9db",
"sha256:807b3096205c7cec861c8803a6738e33ed86c9aae76cac0e19454245a6bbbc0a",
"sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a",
"sha256:87c7c10d186f1743a8fd6a971ab6525d60abd5d5d200f31e073cd5e94d7e7a9d",
"sha256:8b42c0dc927b8d7c0eb59f97e6e34408e53bc489f9f90e66e568f329bff3e443",
"sha256:a00514362df15af041cc06e97aebabf2895e0a7c42c83c21894be12b84402d79",
"sha256:a39efc3ade8c1fb27c097fd112baf09d7fd70b8cb10ef1de4da6efbe066d381d",
"sha256:a4ee8000454ad4486fb9f28b0cab7fa1cd796fc36d639882d0b34109b5b3aec9",
"sha256:a7928283143a401e72a4fad43ecc85b35c27ae699cf5d54d39e1e72d97460e1d",
"sha256:adf4dd19d8875ac147bf926c727215a0faf21490b22c053db464e0bf0deb0485",
"sha256:ae8427a5e9062ba66fc2c62fb19a72276cf12c780e8db2b0956ea909c48acff5",
"sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048",
"sha256:b84f29971f0ad4adaee391c6364e6f780d5aae7e9226d41964b26b49376071d0",
"sha256:c39c46d9e44447181cd502a35aad2bb178dbf1b1f86cf4db639d7b9614f837c6",
"sha256:cb2126603091902767d96bcb74093bd8b14982f41809f85c9b96e519c7e1dc41",
"sha256:dcef843f8de4e2ff5e35e96ec2a4abbdf403bd0f732ead127bd27e51f38ac298",
"sha256:e3447d9e074abf0e3cd85aef8131e01ab93f9f0e86654db7ac8a3f73c63706ce",
"sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0",
"sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"
],
"markers": "python_version >= '3.6'",
"version": "==2.0.0"
},
"hyperlink": {
"hashes": [
"sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b",
"sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4"
],
"version": "==21.0.0"
},
"idna": {
"hashes": [
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
"sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
],
"markers": "python_version >= '3'",
"version": "==3.2"
},
"incremental": {
"hashes": [
"sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57",
"sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321"
],
"version": "==21.3.0"
},
"msgpack": {
"hashes": [
"sha256:0cb94ee48675a45d3b86e61d13c1e6f1696f0183f0715544976356ff86f741d9",
"sha256:1026dcc10537d27dd2d26c327e552f05ce148977e9d7b9f1718748281b38c841",
"sha256:26a1759f1a88df5f1d0b393eb582ec022326994e311ba9c5818adc5374736439",
"sha256:2a5866bdc88d77f6e1370f82f2371c9bc6fc92fe898fa2dec0c5d4f5435a2694",
"sha256:31c17bbf2ae5e29e48d794c693b7ca7a0c73bd4280976d408c53df421e838d2a",
"sha256:497d2c12426adcd27ab83144057a705efb6acc7e85957a51d43cdcf7f258900f",
"sha256:5a9ee2540c78659a1dd0b110f73773533ee3108d4e1219b5a15a8d635b7aca0e",
"sha256:8521e5be9e3b93d4d5e07cb80b7e32353264d143c1f072309e1863174c6aadb1",
"sha256:87869ba567fe371c4555d2e11e4948778ab6b59d6cc9d8460d543e4cfbbddd1c",
"sha256:8ffb24a3b7518e843cd83538cf859e026d24ec41ac5721c18ed0c55101f9775b",
"sha256:92be4b12de4806d3c36810b0fe2aeedd8d493db39e2eb90742b9c09299eb5759",
"sha256:9ea52fff0473f9f3000987f313310208c879493491ef3ccf66268eff8d5a0326",
"sha256:a4355d2193106c7aa77c98fc955252a737d8550320ecdb2e9ac701e15e2943bc",
"sha256:a99b144475230982aee16b3d249170f1cccebf27fb0a08e9f603b69637a62192",
"sha256:ac25f3e0513f6673e8b405c3a80500eb7be1cf8f57584be524c4fa78fe8e0c83",
"sha256:b28c0876cce1466d7c2195d7658cf50e4730667196e2f1355c4209444717ee06",
"sha256:b55f7db883530b74c857e50e149126b91bb75d35c08b28db12dcb0346f15e46e",
"sha256:b6d9e2dae081aa35c44af9c4298de4ee72991305503442a5c74656d82b581fe9",
"sha256:c747c0cc08bd6d72a586310bda6ea72eeb28e7505990f342552315b229a19b33",
"sha256:d6c64601af8f3893d17ec233237030e3110f11b8a962cb66720bf70c0141aa54",
"sha256:d8167b84af26654c1124857d71650404336f4eb5cc06900667a493fc619ddd9f",
"sha256:de6bd7990a2c2dabe926b7e62a92886ccbf809425c347ae7de277067f97c2887",
"sha256:e36a812ef4705a291cdb4a2fd352f013134f26c6ff63477f20235138d1d21009",
"sha256:e89ec55871ed5473a041c0495b7b4e6099f6263438e0bd04ccd8418f92d5d7f2",
"sha256:f3e6aaf217ac1c7ce1563cf52a2f4f5d5b1f64e8729d794165db71da57257f0c",
"sha256:f484cd2dca68502de3704f056fa9b318c94b1539ed17a4c784266df5d6978c87",
"sha256:fae04496f5bc150eefad4e9571d1a76c55d021325dcd484ce45065ebbdd00984",
"sha256:fe07bc6735d08e492a327f496b7850e98cb4d112c56df69b0c844dbebcbb47f6"
],
"version": "==1.0.2"
},
"oauthlib": {
"hashes": [
"sha256:42bf6354c2ed8c6acb54d971fce6f88193d97297e18602a3a886603f9d7730cc",
"sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3"
],
"markers": "python_version >= '3.6'",
"version": "==3.1.1"
},
"pillow": {
"hashes": [
"sha256:0412516dcc9de9b0a1e0ae25a280015809de8270f134cc2c1e32c4eeb397cf30",
"sha256:04835e68ef12904bc3e1fd002b33eea0779320d4346082bd5b24bec12ad9c3e9",
"sha256:06d1adaa284696785375fa80a6a8eb309be722cf4ef8949518beb34487a3df71",
"sha256:085a90a99404b859a4b6c3daa42afde17cb3ad3115e44a75f0d7b4a32f06a6c9",
"sha256:0b9911ec70731711c3b6ebcde26caea620cbdd9dcb73c67b0730c8817f24711b",
"sha256:10e00f7336780ca7d3653cf3ac26f068fa11b5a96894ea29a64d3dc4b810d630",
"sha256:11c27e74bab423eb3c9232d97553111cc0be81b74b47165f07ebfdd29d825875",
"sha256:11eb7f98165d56042545c9e6db3ce394ed8b45089a67124298f0473b29cb60b2",
"sha256:13654b521fb98abdecec105ea3fb5ba863d1548c9b58831dd5105bb3873569f1",
"sha256:15ccb81a6ffc57ea0137f9f3ac2737ffa1d11f786244d719639df17476d399a7",
"sha256:18a07a683805d32826c09acfce44a90bf474e6a66ce482b1c7fcd3757d588df3",
"sha256:19ec4cfe4b961edc249b0e04b5618666c23a83bc35842dea2bfd5dfa0157f81b",
"sha256:1c3ff00110835bdda2b1e2b07f4a2548a39744bb7de5946dc8e95517c4fb2ca6",
"sha256:27a330bf7014ee034046db43ccbb05c766aa9e70b8d6c5260bfc38d73103b0ba",
"sha256:2b11c9d310a3522b0fd3c35667914271f570576a0e387701f370eb39d45f08a4",
"sha256:2c661542c6f71dfd9dc82d9d29a8386287e82813b0375b3a02983feac69ef864",
"sha256:2cde7a4d3687f21cffdf5bb171172070bb95e02af448c4c8b2f223d783214056",
"sha256:2d5e9dc0bf1b5d9048a94c48d0813b6c96fccfa4ccf276d9c36308840f40c228",
"sha256:2f23b2d3079522fdf3c09de6517f625f7a964f916c956527bed805ac043799b8",
"sha256:35d27687f027ad25a8d0ef45dd5208ef044c588003cdcedf05afb00dbc5c2deb",
"sha256:35d409030bf3bd05fa66fb5fdedc39c521b397f61ad04309c90444e893d05f7d",
"sha256:4326ea1e2722f3dc00ed77c36d3b5354b8fb7399fb59230249ea6d59cbed90da",
"sha256:4abc247b31a98f29e5224f2d31ef15f86a71f79c7f4d2ac345a5d551d6393073",
"sha256:4d89a2e9219a526401015153c0e9dd48319ea6ab9fe3b066a20aa9aee23d9fd3",
"sha256:4e59e99fd680e2b8b11bbd463f3c9450ab799305d5f2bafb74fefba6ac058616",
"sha256:548794f99ff52a73a156771a0402f5e1c35285bd981046a502d7e4793e8facaa",
"sha256:56fd98c8294f57636084f4b076b75f86c57b2a63a8410c0cd172bc93695ee979",
"sha256:59697568a0455764a094585b2551fd76bfd6b959c9f92d4bdec9d0e14616303a",
"sha256:6bff50ba9891be0a004ef48828e012babaaf7da204d81ab9be37480b9020a82b",
"sha256:6cb3dd7f23b044b0737317f892d399f9e2f0b3a02b22b2c692851fb8120d82c6",
"sha256:7dbfbc0020aa1d9bc1b0b8bcf255a7d73f4ad0336f8fd2533fcc54a4ccfb9441",
"sha256:838eb85de6d9307c19c655c726f8d13b8b646f144ca6b3771fa62b711ebf7624",
"sha256:8b68f565a4175e12e68ca900af8910e8fe48aaa48fd3ca853494f384e11c8bcd",
"sha256:8f284dc1695caf71a74f24993b7c7473d77bc760be45f776a2c2f4e04c170550",
"sha256:963ebdc5365d748185fdb06daf2ac758116deecb2277ec5ae98139f93844bc09",
"sha256:a048dad5ed6ad1fad338c02c609b862dfaa921fcd065d747194a6805f91f2196",
"sha256:a1bd983c565f92779be456ece2479840ec39d386007cd4ae83382646293d681b",
"sha256:a66566f8a22561fc1a88dc87606c69b84fa9ce724f99522cf922c801ec68f5c1",
"sha256:bcb04ff12e79b28be6c9988f275e7ab69f01cc2ba319fb3114f87817bb7c74b6",
"sha256:bd24054aaf21e70a51e2a2a5ed1183560d3a69e6f9594a4bfe360a46f94eba83",
"sha256:be25cb93442c6d2f8702c599b51184bd3ccd83adebd08886b682173e09ef0c3f",
"sha256:c691b26283c3a31594683217d746f1dad59a7ae1d4cfc24626d7a064a11197d4",
"sha256:cc9d0dec711c914ed500f1d0d3822868760954dce98dfb0b7382a854aee55d19",
"sha256:ce2e5e04bb86da6187f96d7bab3f93a7877830981b37f0287dd6479e27a10341",
"sha256:ce651ca46d0202c302a535d3047c55a0131a720cf554a578fc1b8a2aff0e7d96",
"sha256:d0c8ebbfd439c37624db98f3877d9ed12c137cadd99dde2d2eae0dab0bbfc355",
"sha256:d675a876b295afa114ca8bf42d7f86b5fb1298e1b6bb9a24405a3f6c8338811c",
"sha256:dde3f3ed8d00c72631bc19cbfff8ad3b6215062a5eed402381ad365f82f0c18c",
"sha256:e5a31c07cea5edbaeb4bdba6f2b87db7d3dc0f446f379d907e51cc70ea375629",
"sha256:f514c2717012859ccb349c97862568fdc0479aad85b0270d6b5a6509dbc142e2",
"sha256:fc0db32f7223b094964e71729c0361f93db43664dd1ec86d3df217853cedda87",
"sha256:fd4fd83aa912d7b89b4b4a1580d30e2a4242f3936882a3f433586e5ab97ed0d5",
"sha256:feb5db446e96bfecfec078b943cc07744cc759893cef045aa8b8b6d6aaa8274e"
],
"index": "pypi",
"version": "==8.3.2"
},
"pyasn1": {
"hashes": [
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
"sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
"sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
],
"version": "==0.4.8"
},
"pyasn1-modules": {
"hashes": [
"sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8",
"sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199",
"sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811",
"sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed",
"sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4",
"sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74",
"sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb",
"sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45",
"sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd",
"sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0",
"sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d",
"sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"
],
"version": "==0.2.8"
},
"pycparser": {
"hashes": [
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1,
3.2, 3.3'",
"version": "==2.20"
},
"pyjwt": {
"extras": [
"crypto"
],
"hashes": [
"sha256:934d73fbba91b0483d3857d1aff50e96b2a892384ee2c17417ed3203f173fca1",
"sha256:fba44e7898bbca160a2b2b501f492824fc8382485d3a6f11ba5d0c1937ce6130"
],
"markers": "python_version >= '3.6'",
"version": "==2.1.0"
},
"pyopenssl": {
"hashes": [
"sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51",
"sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"
],
"version": "==20.0.1"
},
"python-dateutil": {
"hashes": [
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1,
3.2, 3.3'",
"version": "==2.8.2"
},
"python3-openid": {
"hashes": [
"sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf",
"sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b"
],
"version": "==3.2.0"
},
"pytz": {
"hashes": [
"sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
"sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
],
"version": "==2021.1"
},
"redis": {
"hashes": [
"sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
"sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"
],
"index": "pypi",
"version": "==3.5.3"
},
"requests": {
"hashes": [
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
],
"index": "pypi",
"version": "==2.26.0"
},
"requests-oauthlib": {
"hashes": [
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a",
"sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"
],
"version": "==1.3.0"
},
"service-identity": {
"hashes": [
"sha256:6e6c6086ca271dc11b033d17c3a8bea9f24ebff920c587da090afc9519419d34",
"sha256:f0b0caac3d40627c3c04d7a51b6e06721857a0e10a8775f2d1d7e72901b3a7db"
],
"version": "==21.1.0"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1,
3.2, 3.3'",
"version": "==1.16.0"
},
"soupsieve": {
"hashes": [
"sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc",
"sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b"
],
"markers": "python_version >= '3.6'",
"version": "==2.2.1"
},
"sqlparse": {
"hashes": [
"sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae",
"sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"
],
"markers": "python_version >= '3.5'",
"version": "==0.4.2"
},
"text-unidecode": {
"hashes": [
"sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8",
"sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"
],
"version": "==1.3"
},
"twisted": {
"extras": [
"tls"
],
"hashes": [
"sha256:13c1d1d2421ae556d91e81e66cf0d4f4e4e1e4a36a0486933bee4305c6a4fb9b",
"sha256:2cd652542463277378b0d349f47c62f20d9306e57d1247baabd6d1d38a109006"
],
"markers": "python_full_version >= '3.6.7'",
"version": "==21.7.0"
},
"twisted-iocpsupport": {
"hashes": [
"sha256:306becd6e22ab6e8e4f36b6bdafd9c92e867c98a5ce517b27fdd27760ee7ae41",
"sha256:3c61742cb0bc6c1ac117a7e5f422c129832f0c295af49e01d8a6066df8cfc04d",
"sha256:72068b206ee809c9c596b57b5287259ea41ddb4774d86725b19f35bf56aa32a9",
"sha256:7d972cfa8439bdcb35a7be78b7ef86d73b34b808c74be56dfa785c8a93b851bf",
"sha256:81b3abe3527b367da0220482820cb12a16c661672b7bcfcde328902890d63323",
"sha256:851b3735ca7e8102e661872390e3bce88f8901bece95c25a0c8bb9ecb8a23d32",
"sha256:985c06a33f5c0dae92c71a036d1ea63872ee86a21dd9b01e1f287486f15524b4",
"sha256:9dbb8823b49f06d4de52721b47de4d3b3026064ef4788ce62b1a21c57c3fff6f",
"sha256:b435857b9efcbfc12f8c326ef0383f26416272260455bbca2cd8d8eca470c546",
"sha256:b76b4eed9b27fd63ddb0877efdd2d15835fdcb6baa745cb85b66e5d016ac2878",
"sha256:b9fed67cf0f951573f06d560ac2f10f2a4bbdc6697770113a2fc396ea2cb2565",
"sha256:bf4133139d77fc706d8f572e6b7d82871d82ec7ef25d685c2351bdacfb701415"
],
"markers": "platform_system == 'Windows'",
"version": "==1.0.2"
},
"txaio": {
"hashes": [
"sha256:7d6f89745680233f1c4db9ddb748df5e88d2a7a37962be174c0fd04c8dba1dc8",
"sha256:c16b55f9a67b2419cfdf8846576e2ec9ba94fe6978a83080c352a80db31c93fb"
],
"markers": "python_version >= '3.6'",
"version": "==21.2.1"
},
"typing-extensions": {
"hashes": [
"sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e",
"sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7",
"sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
],
"version": "==3.10.0.2"
},
"urllib3": {
"hashes": [
"sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
"sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1,
3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.6"
},
"zope.interface": {
"hashes": [
"sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192",
"sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702",
"sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09",
"sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4",
"sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a",
"sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3",
"sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf",
"sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c",
"sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d",
"sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78",
"sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83",
"sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531",
"sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46",
"sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021",
"sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94",
"sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc",
"sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63",
"sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54",
"sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117",
"sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25",
"sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05",
"sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e",
"sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1",
"sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004",
"sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2",
"sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e",
"sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f",
"sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f",
"sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120",
"sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f",
"sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1",
"sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9",
"sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e",
"sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7",
"sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8",
"sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b",
"sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155",
"sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7",
"sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c",
"sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325",
"sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d",
"sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb",
"sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e",
"sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959",
"sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7",
"sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920",
"sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e",
"sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48",
"sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8",
"sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4",
"sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1,
3.2, 3.3, 3.4'",
"version": "==5.4.0"
}
},
"develop": {}
}