Compare commits
No commits in common. "feature/initial_models" and "main" have entirely different histories.
feature/in
...
main
36 changed files with 15 additions and 422 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -396,4 +396,9 @@ pyrightconfig.json
|
||||||
.ionide
|
.ionide
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/django,pycharm,python,visualstudiocode
|
# End of https://www.toptal.com/developers/gitignore/api/django,pycharm,python,visualstudiocode
|
||||||
/.idea
|
/.idea/inspectionProfiles/profiles_settings.xml
|
||||||
|
/.idea/.gitignore
|
||||||
|
/.idea/misc.xml
|
||||||
|
/.idea/mixr-api.iml
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/vcs.xml
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
# Mixr API
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
### Install requirements
|
|
||||||
|
|
||||||
If there are any issues with `psycopg2` and that it cannot be imported, you might be missing `libpq-dev`. If you are using a virtual environment, install it with `sudo apt-get install libpq-dev`.
|
|
||||||
|
|
||||||
If there are any issues with `djangorestframework` or rather the import of `rest_framework`, try installing it again with the active environment using `python -m pip install djangorestframework`.
|
|
|
@ -1,14 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from api_v1.cafeteria.model import Cafeteria
|
|
||||||
from api_v1.institution.model import Institution
|
|
||||||
from api_v1.location.model import Location
|
|
||||||
from api_v1.mood.model import Mood
|
|
||||||
from api_v1.profile.model import Profile
|
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
admin.site.register(Profile)
|
|
||||||
admin.site.register(Location)
|
|
||||||
admin.site.register(Cafeteria)
|
|
||||||
admin.site.register(Institution)
|
|
||||||
admin.site.register(Mood)
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class ApiV1Config(AppConfig):
|
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
|
||||||
name = 'api_v1'
|
|
|
@ -1,25 +0,0 @@
|
||||||
from django.views import View
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from api_v1.profile.profile_services import acreate_profile
|
|
||||||
|
|
||||||
|
|
||||||
class RegistrationView(View):
|
|
||||||
async def post(self, request):
|
|
||||||
# Read parameters from body
|
|
||||||
username = request.POST.get('username')
|
|
||||||
password = request.POST.get('password')
|
|
||||||
email = request.POST.get('email')
|
|
||||||
display_name = request.POST.get('display_name')
|
|
||||||
|
|
||||||
# Check if profile already exists
|
|
||||||
if User.objects.filter(username=username).exists():
|
|
||||||
return HttpResponse(status=400, content="User already exists")
|
|
||||||
|
|
||||||
# Create profile
|
|
||||||
user = await User.objects.acreate_user(username, email, password)
|
|
||||||
|
|
||||||
# Create UserInformation
|
|
||||||
await acreate_profile(user, display_name=display_name)
|
|
||||||
|
|
||||||
return HttpResponse(status=201, content="User created")
|
|
|
@ -1,19 +0,0 @@
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
from ..institution.model import Institution
|
|
||||||
from ..location.model import Location
|
|
||||||
|
|
||||||
|
|
||||||
class Cafeteria(models.Model):
|
|
||||||
cafeteria_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
||||||
|
|
||||||
name = models.CharField(max_length=50, blank=False, null=False)
|
|
||||||
location = models.OneToOneField(Location, on_delete=models.DO_NOTHING)
|
|
||||||
|
|
||||||
website = models.URLField()
|
|
||||||
institutes = models.ManyToManyField(Institution)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '{} ({})'.format(self.name, self.location)
|
|
|
@ -1,22 +0,0 @@
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
from api_v1.location.model import Location
|
|
||||||
|
|
||||||
|
|
||||||
class Institution(models.Model):
|
|
||||||
institution_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
||||||
|
|
||||||
name = models.CharField(max_length=150, blank=False, null=False)
|
|
||||||
description = models.TextField(null=True)
|
|
||||||
|
|
||||||
location = models.OneToOneField(Location, on_delete=models.DO_NOTHING, null=True, blank=True)
|
|
||||||
|
|
||||||
website = models.URLField(null=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
result = self.name
|
|
||||||
if self.description:
|
|
||||||
result += f' - {self.description}'
|
|
||||||
return result
|
|
|
@ -1,22 +0,0 @@
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Location(models.Model):
|
|
||||||
location_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
||||||
|
|
||||||
longitude = models.DecimalField(max_digits=9, decimal_places=6)
|
|
||||||
latitude = models.DecimalField(max_digits=9, decimal_places=6)
|
|
||||||
|
|
||||||
country_code = models.CharField(max_length=2) # Following ISO-3166-1
|
|
||||||
country_name = models.CharField(max_length=50)
|
|
||||||
|
|
||||||
city = models.CharField(max_length=50)
|
|
||||||
postal_code = models.CharField(max_length=20)
|
|
||||||
street = models.CharField(max_length=50)
|
|
||||||
number = models.CharField(max_length=20)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '{} {}, {} {}, {} {}'.format(self.street, self.number, self.postal_code, self.city, self.country_code,
|
|
||||||
self.country_name)
|
|
|
@ -1,24 +0,0 @@
|
||||||
# Generated by Django 5.0.2 on 2024-02-28 12:44
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('auth', '0012_alter_user_first_name_max_length'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='UserInformation',
|
|
||||||
fields=[
|
|
||||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
|
|
||||||
('display_name', models.CharField(max_length=150)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,19 +0,0 @@
|
||||||
# Generated by Django 5.0.2 on 2024-02-28 19:41
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api_v1', '0001_createBasicUserInformation'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameModel(
|
|
||||||
old_name='UserInformation',
|
|
||||||
new_name='Profile',
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,28 +0,0 @@
|
||||||
# Generated by Django 5.0.2 on 2024-02-28 20:03
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api_v1', '0002_rebrandUserInformationToProfile'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Location',
|
|
||||||
fields=[
|
|
||||||
('location_id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('longitude', models.DecimalField(decimal_places=6, max_digits=9)),
|
|
||||||
('latitude', models.DecimalField(decimal_places=6, max_digits=9)),
|
|
||||||
('country_code', models.CharField(max_length=2)),
|
|
||||||
('country_name', models.CharField(max_length=50)),
|
|
||||||
('city', models.CharField(max_length=50)),
|
|
||||||
('postal_code', models.CharField(max_length=20)),
|
|
||||||
('street', models.CharField(max_length=50)),
|
|
||||||
('number', models.CharField(max_length=20)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,24 +0,0 @@
|
||||||
# Generated by Django 5.0.2 on 2024-02-28 20:03
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api_v1', '0003_createdLocationModel'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Cafeteria',
|
|
||||||
fields=[
|
|
||||||
('cafeteria_id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=50)),
|
|
||||||
('website', models.URLField()),
|
|
||||||
('location', models.OneToOneField(on_delete=django.db.models.deletion.DO_NOTHING, to='api_v1.location')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,30 +0,0 @@
|
||||||
# Generated by Django 5.0.2 on 2024-02-28 20:23
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api_v1', '0004_createdCafeteriaModel'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Institution',
|
|
||||||
fields=[
|
|
||||||
('institution_id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=150)),
|
|
||||||
('description', models.TextField(null=True)),
|
|
||||||
('website', models.URLField(null=True)),
|
|
||||||
('location', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='api_v1.location')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='cafeteria',
|
|
||||||
name='institutes',
|
|
||||||
field=models.ManyToManyField(to='api_v1.institution'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,27 +0,0 @@
|
||||||
# Generated by Django 5.0.2 on 2024-02-28 22:55
|
|
||||||
|
|
||||||
import colorfield.fields
|
|
||||||
import uuid
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api_v1', '0005_addedInstitutionModel'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Mood',
|
|
||||||
fields=[
|
|
||||||
('mood_id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=30)),
|
|
||||||
('description', models.CharField(max_length=150)),
|
|
||||||
('color', colorfield.fields.ColorField(default='#FFFFFF', image_field=None, max_length=25, samples=None)),
|
|
||||||
('is_available', models.BooleanField(default=True)),
|
|
||||||
('is_reliable', models.BooleanField(default=True)),
|
|
||||||
('is_going', models.BooleanField(default=False)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,5 +0,0 @@
|
||||||
from api_v1.profile.model import Profile
|
|
||||||
from api_v1.location.model import Location
|
|
||||||
from api_v1.cafeteria.model import Cafeteria
|
|
||||||
from api_v1.institution.model import Institution
|
|
||||||
from api_v1.mood.model import Mood
|
|
|
@ -1,20 +0,0 @@
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from colorfield.fields import ColorField
|
|
||||||
|
|
||||||
|
|
||||||
class Mood(models.Model):
|
|
||||||
mood_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
||||||
|
|
||||||
name = models.CharField(max_length=30)
|
|
||||||
description = models.CharField(max_length=150, blank=False, null=False)
|
|
||||||
|
|
||||||
color = ColorField(format='hex')
|
|
||||||
|
|
||||||
is_available = models.BooleanField(default=True) # Is available to go
|
|
||||||
is_reliable = models.BooleanField(default=True) # Will always do the same in this mood
|
|
||||||
is_going = models.BooleanField(default=False) # Is going to the cafeteria
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
|
@ -1,11 +0,0 @@
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Profile(models.Model):
|
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
|
||||||
|
|
||||||
display_name = models.CharField(max_length=150, null=False)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '{} ({})'.format(self.display_name, self.user)
|
|
|
@ -1,48 +0,0 @@
|
||||||
from api_v1.profile.model import Profile
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
async def acreate_profile(user: User, *, display_name: Optional[str] = None) -> Profile:
|
|
||||||
"""Async. Create a Profile object for the given profile.
|
|
||||||
|
|
||||||
@param user: The profile to create the Profile object for.
|
|
||||||
@param display_name: The display name for the profile. If None, the profile's username will be used.
|
|
||||||
@return: The created Profile object.
|
|
||||||
"""
|
|
||||||
profile = await Profile.objects.acreate(user=user, display_name=user.username)
|
|
||||||
|
|
||||||
# Use the given display name if provided.
|
|
||||||
if display_name is not None:
|
|
||||||
profile.display_name = display_name
|
|
||||||
await profile.asave()
|
|
||||||
|
|
||||||
return profile
|
|
||||||
|
|
||||||
|
|
||||||
async def aget_profile(user: User) -> Profile:
|
|
||||||
"""Async. Get the Profile object for the given profile.
|
|
||||||
|
|
||||||
@param user: The profile to get the Profile object for.
|
|
||||||
@return: The Profile object for the given profile.
|
|
||||||
"""
|
|
||||||
return await Profile.objects.aget(user)
|
|
||||||
|
|
||||||
|
|
||||||
async def aset_user_display_name(user: User, display_name: Optional[str] = None) -> Profile:
|
|
||||||
"""Async. Set the display name for the given profile.
|
|
||||||
|
|
||||||
@param user: The profile to set the display name for.
|
|
||||||
@param display_name: The display name to set for the profile. If None, the profile's username will be used.
|
|
||||||
@return: The Profile object for the given profile.
|
|
||||||
"""
|
|
||||||
profile = await Profile.objects.aget(user)
|
|
||||||
|
|
||||||
# Set the display name to the given display name, or the profile's username if None.
|
|
||||||
if display_name is not None:
|
|
||||||
profile.display_name = display_name
|
|
||||||
else:
|
|
||||||
profile.display_name = user.username
|
|
||||||
|
|
||||||
await profile.asave()
|
|
||||||
return profile
|
|
|
@ -1,13 +0,0 @@
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
from api_v1.profile.model import Profile
|
|
||||||
|
|
||||||
|
|
||||||
class Schedule(models.Model):
|
|
||||||
schedule_id = models.UUIDField(primary_key=True, default=uuid.uuid4)
|
|
||||||
|
|
||||||
profile = models.ForeignKey(Profile, on_delete=models.DO_NOTHING, null=False)
|
|
||||||
|
|
||||||
slots = models.ManyToOneRel()
|
|
|
@ -1,34 +0,0 @@
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
def validate_weekday(value: int):
|
|
||||||
if value < 0 or value > 6:
|
|
||||||
raise ValidationError("Weekday must be between 0 (Mo) and 6 (Su)")
|
|
||||||
|
|
||||||
|
|
||||||
class Slot(models.Model):
|
|
||||||
slot_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
||||||
|
|
||||||
start_time = models.TimeField()
|
|
||||||
end_time = models.TimeField()
|
|
||||||
|
|
||||||
duration = models.DurationField()
|
|
||||||
|
|
||||||
start_weekday = models.SmallIntegerField(validators=[validate_weekday])
|
|
||||||
end_weekday = models.SmallIntegerField(validators=[validate_weekday])
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
days = [
|
|
||||||
"Mo",
|
|
||||||
"Tu",
|
|
||||||
"We",
|
|
||||||
"Th",
|
|
||||||
"Fr",
|
|
||||||
"Sa",
|
|
||||||
"Su"
|
|
||||||
]
|
|
||||||
return "[{}] {} - [{}] {}".format(days[self.start_weekday], self.start_time, days[self.end_weekday],
|
|
||||||
self.end_time)
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
|
@ -1,5 +0,0 @@
|
||||||
from django.urls import path, include
|
|
||||||
|
|
||||||
# Assign register url
|
|
||||||
urlpatterns = [
|
|
||||||
]
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
|
@ -15,6 +15,7 @@ from pathlib import Path
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
@ -35,10 +37,6 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
|
||||||
'api_v1.apps.ApiV1Config',
|
|
||||||
'rest_framework',
|
|
||||||
'colorfield',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -71,6 +69,7 @@ TEMPLATES = [
|
||||||
|
|
||||||
WSGI_APPLICATION = 'mixr_api.wsgi.application'
|
WSGI_APPLICATION = 'mixr_api.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
||||||
|
|
||||||
|
@ -85,6 +84,7 @@ DATABASES = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
@ -103,6 +103,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
||||||
|
|
||||||
|
@ -114,6 +115,7 @@ USE_I18N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,8 @@ Including another URLconf
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path("v1/", include("api_v1.urls")),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,2 @@
|
||||||
Django~=4.2
|
Django~=5.0.2
|
||||||
psycopg2-binary~=2.9.9
|
psycopg2-binary~=2.9.9
|
||||||
djangorestframework~=3.14.0
|
|
||||||
django-colorfield~=0.11.0
|
|
Loading…
Reference in a new issue