Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
08e9541177
|
|
@ -3,6 +3,7 @@ ITRadio/ITRadioBackend/db.sqlite3
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
conf.dev/
|
||||||
/conf
|
/conf
|
||||||
var
|
var
|
||||||
server/proj/media
|
server/proj/media
|
||||||
|
|
@ -17,4 +18,4 @@ server/proj/conf/settings/__init__.py
|
||||||
__pycache__
|
__pycache__
|
||||||
media/
|
media/
|
||||||
env/
|
env/
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
from rest_framework.schemas import AutoSchema
|
||||||
|
import coreapi
|
||||||
|
import coreschema
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateUserSchema(AutoSchema):
|
||||||
|
def get_serializer_fields(self, path, method):
|
||||||
|
return [
|
||||||
|
coreapi.Field(
|
||||||
|
name='email',
|
||||||
|
location='form',
|
||||||
|
required=False,
|
||||||
|
schema=coreschema.String(description='Email пользователя')
|
||||||
|
),
|
||||||
|
coreapi.Field(
|
||||||
|
name='old_password',
|
||||||
|
location='form',
|
||||||
|
required=False,
|
||||||
|
schema=coreschema.String(description='Старый пароль')
|
||||||
|
),
|
||||||
|
coreapi.Field(
|
||||||
|
name='password',
|
||||||
|
location='form',
|
||||||
|
required=False,
|
||||||
|
schema=coreschema.String(description='Новый пароль')
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -11,11 +11,7 @@ class MyUserSerializer(ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MyUser
|
model = MyUser
|
||||||
fields = ('id', 'email', 'password',
|
fields = ('id', 'email', 'password', 'date_joined')
|
||||||
'first_name', 'last_name',
|
|
||||||
'phone', 'date_joined',
|
|
||||||
'number',
|
|
||||||
)
|
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'password': {'write_only': True},
|
'password': {'write_only': True},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,21 @@ from rest_framework_simplejwt.views import TokenObtainPairView
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.contrib.auth.hashers import make_password
|
from django.contrib.auth.hashers import make_password, check_password
|
||||||
from conf import settings
|
from conf import settings
|
||||||
from account.serializers import MyUserSerializer, MyTokenObtainPairSerializer
|
from account.serializers import MyUserSerializer, MyTokenObtainPairSerializer
|
||||||
from account.models import MyUser
|
from account.models import MyUser
|
||||||
|
from .schemas import UpdateUserSchema
|
||||||
|
from conf.settings.base import MIN_LEN_PASSWORD
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.validators import validate_email
|
||||||
|
|
||||||
PermissionClass = IsAuthenticated if not settings.DEBUG else AllowAny
|
PermissionClass = IsAuthenticated if not settings.DEBUG else AllowAny
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MyTokenObtainPairView(TokenObtainPairView):
|
class MyTokenObtainPairView(TokenObtainPairView):
|
||||||
permission_classes = [AllowAny]
|
permission_classes = [AllowAny]
|
||||||
serializer_class = MyTokenObtainPairSerializer
|
serializer_class = MyTokenObtainPairSerializer
|
||||||
|
|
@ -53,17 +62,45 @@ class MyUserViewSet(ViewSet):
|
||||||
token_serializer.is_valid(raise_exception=True)
|
token_serializer.is_valid(raise_exception=True)
|
||||||
return Response(token_serializer.validated_data, status=status.HTTP_201_CREATED)
|
return Response(token_serializer.validated_data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
@action(detail=False, methods=['post'])
|
@action(detail=False, methods=['post'], schema=UpdateUserSchema())
|
||||||
def update_user(self, request):
|
def update_user(self, request):
|
||||||
if 'email' in request.data:
|
password = request.user.password
|
||||||
del request.data['email']
|
|
||||||
if 'password' in request.data:
|
|
||||||
request.data['password'] = make_password(request.data['password'])
|
|
||||||
serializer = MyUserSerializer(request.user, data=request.data, partial=True)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
serializer.save()
|
|
||||||
|
|
||||||
return Response(serializer.data)
|
if check_password(request.data['old_password'], password):
|
||||||
|
try:
|
||||||
|
validate_email(request.data['password'])
|
||||||
|
except ValidationError as e:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return Response(
|
||||||
|
{'detail': 'Почта не может являться паролем', 'error': {'email': 'Почта не может являться паролем'}},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
if len(request.data['password']) < MIN_LEN_PASSWORD:
|
||||||
|
return Response(
|
||||||
|
{'detail': 'Минимальная длина - 8 символов', 'error': {'email': 'Минимальная длина - 8 символов'}},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
if check_password(request.data['password'], password):
|
||||||
|
return Response(
|
||||||
|
{'detail': 'Пароли одинаковые', 'error': {'email': 'Пароли одинаковые'}},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if 'email' in request.data:
|
||||||
|
del request.data['email']
|
||||||
|
if 'password' in request.data:
|
||||||
|
request.data['password'] = make_password(request.data['password'])
|
||||||
|
serializer = MyUserSerializer(request.user, data=request.data, partial=True)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
serializer.save()
|
||||||
|
return Response(serializer.data)
|
||||||
|
else:
|
||||||
|
return Response(
|
||||||
|
{'detail': 'Неверный старый пароль', 'error': {'email': 'Неверный старый пароль'}},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
@action(detail=False, methods=['post'])
|
@action(detail=False, methods=['post'])
|
||||||
def password_reset_user(self, request):
|
def password_reset_user(self, request):
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
# Generated by Django 5.0.4 on 2024-04-21 17:43
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='NowPlayingSong',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('title', models.CharField(max_length=255)),
|
|
||||||
('artist', models.CharField(max_length=255)),
|
|
||||||
('album', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
('genre', models.CharField(blank=True, max_length=100, null=True)),
|
|
||||||
('art_url', models.URLField(blank=True, null=True)),
|
|
||||||
('duration', models.IntegerField()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# Generated by Django 5.0.4 on 2024-04-21 18:13
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='nowplayingsong',
|
|
||||||
name='duration',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.0.4 on 2024-04-21 18:51
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0002_remove_nowplayingsong_duration'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='nowplayingsong',
|
|
||||||
old_name='art_url',
|
|
||||||
new_name='art',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
# Generated by Django 5.0.4 on 2024-04-24 14:17
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0003_rename_art_url_nowplayingsong_art'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='NowPlayingSong',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Song, FavoriteSong
|
from .models import Song, FavoriteSong, PlayList
|
||||||
|
|
||||||
@admin.register(Song)
|
@admin.register(Song)
|
||||||
class SongAdmin(admin.ModelAdmin):
|
class SongAdmin(admin.ModelAdmin):
|
||||||
|
|
@ -9,3 +9,7 @@ class SongAdmin(admin.ModelAdmin):
|
||||||
@admin.register(FavoriteSong)
|
@admin.register(FavoriteSong)
|
||||||
class FavoriteSongAdmin(admin.ModelAdmin):
|
class FavoriteSongAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id', 'song', 'user')
|
list_display = ('id', 'song', 'user')
|
||||||
|
|
||||||
|
@admin.register(PlayList)
|
||||||
|
class PlayListAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('id', 'name', 'user', 'art')
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ class Song(models.Model):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Треки'
|
verbose_name = 'Треки'
|
||||||
|
unique_together = ('azura_id', )
|
||||||
verbose_name_plural = 'Треки'
|
verbose_name_plural = 'Треки'
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -36,3 +37,15 @@ class FavoriteSong(models.Model):
|
||||||
verbose_name = 'Избранные Треки'
|
verbose_name = 'Избранные Треки'
|
||||||
unique_together = ('song', 'user')
|
unique_together = ('song', 'user')
|
||||||
verbose_name_plural = 'Избранные Треки'
|
verbose_name_plural = 'Избранные Треки'
|
||||||
|
|
||||||
|
|
||||||
|
class PlayList(models.Model):
|
||||||
|
name = models.CharField('Название плейлиста', max_length=50, blank=True, null=True)
|
||||||
|
song = models.ManyToManyField(Song, blank=True, null=True)
|
||||||
|
user = models.ForeignKey(MyUser, verbose_name='Пользователь', on_delete=models.CASCADE)
|
||||||
|
art = models.ImageField('Изображение плейлиста', blank=True, null=True, upload_to="playlist_images/")
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Плейлисты'
|
||||||
|
verbose_name_plural = 'Плейлисты'
|
||||||
|
|
@ -52,4 +52,27 @@ class DeleteSongSchema(AutoSchema):
|
||||||
required=False,
|
required=False,
|
||||||
schema=coreschema.String(description='ID трека с Азуры')
|
schema=coreschema.String(description='ID трека с Азуры')
|
||||||
),
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
class PlayListSchema(AutoSchema):
|
||||||
|
def get_serializer_fields(self, path, method):
|
||||||
|
return [
|
||||||
|
coreapi.Field(
|
||||||
|
name='name',
|
||||||
|
location='form',
|
||||||
|
required=False,
|
||||||
|
schema=coreschema.String(description='Название плейлиста')
|
||||||
|
),
|
||||||
|
coreapi.Field(
|
||||||
|
name='playlist_id',
|
||||||
|
location='form',
|
||||||
|
required=False,
|
||||||
|
schema=coreschema.Integer(description='ID плейлиста')
|
||||||
|
),
|
||||||
|
coreapi.Field(
|
||||||
|
name='azura_id',
|
||||||
|
location='form',
|
||||||
|
required=False,
|
||||||
|
schema=coreschema.String(description='ID трека с азуры')
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
@ -1,11 +1,24 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import Song, FavoriteSong
|
from .models import Song, FavoriteSong, PlayList
|
||||||
|
|
||||||
class SongSerializer(serializers.ModelSerializer):
|
class SongSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Song
|
model = Song
|
||||||
fields = ('id', 'unique_id', 'azura_id', 'title', 'artist', 'album', 'genre', 'art')
|
fields = ('id', 'unique_id', 'azura_id', 'title', 'artist', 'album', 'genre', 'art')
|
||||||
|
|
||||||
|
class PlayListSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = PlayList
|
||||||
|
fields = ('id', 'name', 'song', 'user')
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
rep = super().to_representation(instance)
|
||||||
|
rep["song"] = SongSerializer(
|
||||||
|
instance.song.all(), many=True).data
|
||||||
|
return rep
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class FavoriteSongSerializer(serializers.ModelSerializer):
|
class FavoriteSongSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,98 @@ from rest_framework.decorators import action
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
||||||
from django.shortcuts import get_object_or_404, get_list_or_404
|
from django.shortcuts import get_object_or_404, get_list_or_404
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||||
import requests
|
import requests
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
|
||||||
from .schemas import SongSchema, DeleteSongSchema
|
from .schemas import SongSchema, DeleteSongSchema, PlayListSchema
|
||||||
from .models import Song, FavoriteSong
|
from .models import Song, FavoriteSong, PlayList
|
||||||
from .serializers import SongSerializer, FavoriteSongSerializer
|
from .serializers import SongSerializer, FavoriteSongSerializer, PlayListSerializer
|
||||||
|
|
||||||
|
class PlayListViewSet(GenericViewSet):
|
||||||
|
queryset = PlayList
|
||||||
|
serializer_class = PlayListSerializer
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
def list(self, request):
|
||||||
|
queryset = self.get_queryset().objects.filter(user=request.user)
|
||||||
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def retrieve(self, request, pk):
|
||||||
|
try:
|
||||||
|
queryset = self.get_queryset().objects.get(pk=pk, user=request.user)
|
||||||
|
serializer = self.get_serializer(queryset)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{'detail': 'Объекта не существует', 'error': {'PlayList': 'Объекта не существует'}},
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
@action(detail=False, methods=['post'], schema=PlayListSchema())
|
||||||
|
def create_playlist(self, request):
|
||||||
|
|
||||||
|
if request.data.get('name', False):
|
||||||
|
try:
|
||||||
|
instance = PlayList.objects.get(pk=request.data['playlist_id'], user=request.user.pk)
|
||||||
|
data = request.data
|
||||||
|
data.update(user=request.user.pk)
|
||||||
|
serializer = self.get_serializer(data=request.data, instance=instance)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
serializer.save()
|
||||||
|
return Response(serializer.data)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{'detail': 'Объекта не существует', 'error': {'PlayList': 'Объекта не существует'}},
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
else:
|
||||||
|
playlist_pk = self.get_queryset().objects.filter(user=request.user.pk)
|
||||||
|
number_playlist = len(playlist_pk) + 1
|
||||||
|
name = f"Плейлист № {number_playlist}"
|
||||||
|
data = request.data
|
||||||
|
data.update(user=request.user.pk, name=name)
|
||||||
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
serializer.save()
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@action(detail=False, methods=['post'], schema=PlayListSchema())
|
||||||
|
def delete_playlist(self, request):
|
||||||
|
try:
|
||||||
|
item = PlayList.objects.get(user=request.user, pk=request.data.get('playlist_id'))
|
||||||
|
item.delete()
|
||||||
|
return Response(status=status.HTTP_202_ACCEPTED)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{'detail': 'Объекта не существует', 'error': {'PlayList': 'Объекта не существует'}},
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
@action(detail=False, methods=['post'], schema=PlayListSchema())
|
||||||
|
def add_to_playlist(self, request):
|
||||||
|
try:
|
||||||
|
song = list(Song.objects.filter(azura_id=request.data.get('azura_id')).values_list('pk', flat=True))
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{'detail': 'Песни не существует', 'error': {'Song': 'Песни не существует'}},
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
try:
|
||||||
|
instance = PlayList.objects.get(pk=request.data.get('playlist_id'))
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{'detail': 'Плейлиста не существует', 'error': {'PlayList': 'Плейлиста не существует'}},
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
songs = list(instance.song.all().values_list('pk', flat=True))+song
|
||||||
|
data = {
|
||||||
|
'playlist_id': request.data.get('playlist_id'),
|
||||||
|
'song': songs
|
||||||
|
}
|
||||||
|
serializer = PlayListSerializer(data=data, partial=True, instance=instance)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
serializer.save()
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class SongViewSet(GenericViewSet):
|
class SongViewSet(GenericViewSet):
|
||||||
queryset = Song.objects.all()
|
queryset = Song.objects.all()
|
||||||
serializer_class = SongSerializer
|
serializer_class = SongSerializer
|
||||||
|
|
@ -108,6 +192,24 @@ class SongViewSet(GenericViewSet):
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
return Response({"error": 'Объекта не существует'}, status=status.HTTP_404_NOT_FOUND)
|
return Response({"error": 'Объекта не существует'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
@action(detail=False, methods=['get'])
|
||||||
|
def get_all_song(self, request):
|
||||||
|
file_url = "http://82.97.242.49:10084/api/station/1/files"
|
||||||
|
API_KEY = "49226d3488aac3f5:18d88659c6c1c5e131a0ce0a94d55235"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {API_KEY}"
|
||||||
|
}
|
||||||
|
response = requests.get(file_url, headers=headers)
|
||||||
|
data = []
|
||||||
|
for i in response.json():
|
||||||
|
i['azura_id'] = i.pop('song_id')
|
||||||
|
data.append(i)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
from .local import *
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
|
from .local import *
|
||||||
from .development import *
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -33,12 +33,12 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'corsheaders',
|
'corsheaders',
|
||||||
'rest_framework_simplejwt',
|
'rest_framework_simplejwt',
|
||||||
'rest_framework.authtoken',
|
'rest_framework.authtoken',
|
||||||
|
|
||||||
'news',
|
'news',
|
||||||
'rubricks',
|
'rubricks',
|
||||||
'loginApi',
|
'loginApi',
|
||||||
|
|
@ -47,7 +47,7 @@ INSTALLED_APPS = [
|
||||||
'api',
|
'api',
|
||||||
'account',
|
'account',
|
||||||
'config_site'
|
'config_site'
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|
@ -132,38 +132,7 @@ LOGGING = {
|
||||||
'style': '{',
|
'style': '{',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'handlers': {
|
|
||||||
'update_history': {
|
|
||||||
'level': 'DEBUG',
|
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
|
||||||
'filename': LOG_ROOT / 'update_history.log',
|
|
||||||
'formatter': 'extended',
|
|
||||||
'maxBytes': ROTATE_LOG_SIZE,
|
|
||||||
'backupCount': ROTATE_LOG_COUNT,
|
|
||||||
},
|
|
||||||
'upload_media': {
|
|
||||||
'level': 'DEBUG',
|
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
|
||||||
'filename': LOG_ROOT / 'upload_media.log',
|
|
||||||
'formatter': 'extended',
|
|
||||||
'maxBytes': ROTATE_LOG_SIZE,
|
|
||||||
'backupCount': ROTATE_LOG_COUNT,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'loggers': {
|
|
||||||
'update_history': {
|
|
||||||
'handlers': ['update_history', ],
|
|
||||||
'level': 'INFO',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
'upload_media': {
|
|
||||||
'handlers': ['upload_media', ],
|
|
||||||
'level': 'INFO',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'DEFAULT_PERMISSION_CLASSES': [
|
'DEFAULT_PERMISSION_CLASSES': [
|
||||||
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
|
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
|
||||||
|
|
@ -191,3 +160,4 @@ CORS_ALLOW_CREDENTIALS = True
|
||||||
CORS_ALLOWED_ORIGINS = [
|
CORS_ALLOWED_ORIGINS = [
|
||||||
'http://localhost:5173',
|
'http://localhost:5173',
|
||||||
]
|
]
|
||||||
|
MIN_LEN_PASSWORD = 8
|
||||||
|
|
@ -6,7 +6,7 @@ ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||||
""" DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||||
'NAME': 'it_radio',
|
'NAME': 'it_radio',
|
||||||
|
|
@ -15,12 +15,6 @@ ALLOWED_HOSTS = ['*']
|
||||||
'HOST': 'localhost',
|
'HOST': 'localhost',
|
||||||
'PORT': '5433',
|
'PORT': '5433',
|
||||||
},
|
},
|
||||||
} """
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
|
||||||
'NAME': BASE_DIR / 'db.sqlite3',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] = ('rest_framework.permissions.AllowAny',)
|
REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] = ('rest_framework.permissions.AllowAny',)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import sys
|
||||||
|
from .base import *
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] = ('rest_framework.permissions.AllowAny',)
|
||||||
|
|
||||||
|
if len(sys.argv) >= 2 and not sys.argv[0].endswith('manage.py'):
|
||||||
|
from conf.sentry import sentry_start, SENTRY_CONFIG
|
||||||
|
|
||||||
|
SENTRY_CONFIG['environment'] = 'local'
|
||||||
|
sentry_start(SENTRY_CONFIG)
|
||||||
|
|
@ -2,19 +2,20 @@ import sys
|
||||||
from .base import *
|
from .base import *
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
ALLOWED_HOSTS = ['*']
|
ALLOWED_HOSTS = ['82.97.242.49']
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||||
""" DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||||
'NAME': 'ldirect',
|
'NAME': 'itradio',
|
||||||
'USER': 'flexites',
|
'USER': 'postgres',
|
||||||
'PASSWORD': 'flexites',
|
'PASSWORD': '55667788Vxod',
|
||||||
'HOST': 'localhost',
|
'HOST': 'localhost',
|
||||||
|
'PORT': '5432',
|
||||||
},
|
},
|
||||||
} """
|
}
|
||||||
|
|
||||||
if len(sys.argv) >= 2 and sys.argv[1] != 'runserver':
|
if len(sys.argv) >= 2 and sys.argv[1] != 'runserver':
|
||||||
from conf.sentry import sentry_start, SENTRY_CONFIG
|
from conf.sentry import sentry_start, SENTRY_CONFIG
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,13 @@ from userProfile.views import TeamViewSet
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from rubricks.views import RubricViewSet
|
from rubricks.views import RubricViewSet
|
||||||
from api.views import FetchAndServeFile
|
from api.views import FetchAndServeFile
|
||||||
from audio.views import SongViewSet
|
from audio.views import SongViewSet, PlayListViewSet
|
||||||
|
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.register(r'teams', TeamViewSet, basename='teams')
|
router.register(r'teams', TeamViewSet, basename='teams')
|
||||||
router.register(r'rubriks', RubricViewSet, basename='rubriks')
|
router.register(r'rubriks', RubricViewSet, basename='rubriks')
|
||||||
|
router.register(r'playlists', PlayListViewSet, basename='playlists')
|
||||||
router.register(r'song', SongViewSet, basename='song')
|
router.register(r'song', SongViewSet, basename='song')
|
||||||
router.register(r'fetchandservefile', FetchAndServeFile, basename='fetchandservefile')
|
router.register(r'fetchandservefile', FetchAndServeFile, basename='fetchandservefile')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
# Generated by Django 5.0.4 on 2024-04-16 15:16
|
# Generated by Django 5.0.6 on 2024-06-19 12:09
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -12,13 +13,11 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='News',
|
name='HistoryRadio',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=255)),
|
('description', models.CharField(max_length=50, verbose_name='Описание события кратко')),
|
||||||
('description', models.TextField()),
|
('date', models.DateField(default=django.utils.timezone.now, null=True, verbose_name='Дата события')),
|
||||||
('date', models.DateTimeField()),
|
|
||||||
('author', models.CharField(max_length=255)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# Generated by Django 5.0.4 on 2024-04-16 15:47
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('news', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='news',
|
|
||||||
name='author',
|
|
||||||
field=models.CharField(blank=True, max_length=255),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='news',
|
|
||||||
name='date',
|
|
||||||
field=models.DateTimeField(blank=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.0.4 on 2024-04-16 15:16
|
# Generated by Django 5.0.6 on 2024-06-19 12:09
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
@ -14,10 +14,15 @@ class Migration(migrations.Migration):
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Rubric',
|
name='Rubric',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=255)),
|
('name', models.CharField(max_length=255, verbose_name='Название рубрики')),
|
||||||
('description', models.TextField()),
|
('title', models.TextField(default='Null', max_length=1000, null=True, verbose_name='Заголовок рубрики')),
|
||||||
('time', models.DateTimeField()),
|
('description', models.TextField(max_length=1000, verbose_name='Описание рубрики')),
|
||||||
|
('img', models.ImageField(blank=True, null=True, upload_to='images/', verbose_name='Изображение рубрики')),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Рубрики',
|
||||||
|
'verbose_name_plural': 'Рубрики',
|
||||||
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.0.4 on 2024-04-16 16:10
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('rubricks', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='rubric',
|
|
||||||
name='time',
|
|
||||||
field=models.DateTimeField(blank=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# Generated by Django 5.0.4 on 2024-05-06 19:19
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('rubricks', '0002_alter_rubric_time'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='rubric',
|
|
||||||
name='time',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,13 +1,9 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from rest_framework.authtoken.admin import TokenAdmin
|
from rest_framework.authtoken.admin import TokenAdmin
|
||||||
from .models import Team
|
from .models import Team
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
TokenAdmin.raw_id_fields = ['user']
|
TokenAdmin.raw_id_fields = ['user']
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Team)
|
@admin.register(Team)
|
||||||
class TeamAdmin(admin.ModelAdmin):
|
class TeamAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id', 'name', 'last_name', 'position', 'img_person')
|
list_display = ('id', 'name', 'last_name', 'position', 'img_person')
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
# Generated by Django 5.0.4 on 2024-04-25 14:58
|
# Generated by Django 5.0.6 on 2024-06-19 12:09
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -10,16 +8,21 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Profile',
|
name='Team',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('likedSongs', models.JSONField(default=list)),
|
('name', models.CharField(max_length=50, verbose_name='Имя участника')),
|
||||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('last_name', models.CharField(max_length=50, verbose_name='Фамилия участника')),
|
||||||
|
('position', models.CharField(max_length=50, verbose_name='Должность участинка')),
|
||||||
|
('img_person', models.ImageField(blank=True, null=True, upload_to='team_images/', verbose_name='Изображение участника')),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Команда',
|
||||||
|
'verbose_name_plural': 'Команда',
|
||||||
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.0.4 on 2024-05-06 19:19
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('userProfile', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='profile',
|
|
||||||
name='likedSongs',
|
|
||||||
field=models.JSONField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -2,34 +2,6 @@ from django.contrib.auth.models import User
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import JSONField
|
from django.db.models import JSONField
|
||||||
|
|
||||||
""" class Profile(models.Model):
|
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
likedSongs = JSONField(blank=True, null=True)
|
|
||||||
"""
|
|
||||||
|
|
||||||
# likedSongs = models.ManyToManyField(Song)
|
|
||||||
# likedSongs = models.TextField()
|
|
||||||
# podcasts = models.ManyToManyField(Podcast)
|
|
||||||
# playlists = models.ManyToManyField(Playlist)
|
|
||||||
|
|
||||||
|
|
||||||
# #JWT
|
|
||||||
# from django.contrib.auth.models import User
|
|
||||||
# from rest_framework.authtoken.models import Token
|
|
||||||
|
|
||||||
# for user in User.objects.all():
|
|
||||||
# Token.objects.get_or_create(user=user)
|
|
||||||
|
|
||||||
# from django.conf import settings
|
|
||||||
# from django.db.models.signals import post_save
|
|
||||||
# from django.dispatch import receiver
|
|
||||||
|
|
||||||
# @receiver(post_save, sender=settings.AUTH_USER_MODEL)
|
|
||||||
# def create_auth_token(sender, instance=None, created=False, **kwargs):
|
|
||||||
# if created:
|
|
||||||
# Token.objects.create(user=instance)
|
|
||||||
|
|
||||||
class Team(models.Model):
|
class Team(models.Model):
|
||||||
name = models.CharField('Имя участника', max_length=50)
|
name = models.CharField('Имя участника', max_length=50)
|
||||||
last_name = models.CharField('Фамилия участника', max_length=50)
|
last_name = models.CharField('Фамилия участника', max_length=50)
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,6 @@ from rest_framework import serializers, views, status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from .models import Team
|
from .models import Team
|
||||||
|
|
||||||
""" class ProfileSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Profile
|
|
||||||
fields = ['user', 'likedSongs']
|
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
new_song = validated_data.get('likedSongs')
|
|
||||||
if new_song:
|
|
||||||
current_songs = instance.likedSongs or []
|
|
||||||
# Check if the song ID already exists in the current songs
|
|
||||||
if not any(song['id'] == new_song['id'] for song in current_songs):
|
|
||||||
current_songs.append(new_song)
|
|
||||||
instance.likedSongs = current_songs
|
|
||||||
instance.save()
|
|
||||||
return instance
|
|
||||||
def create(self, validated_data):
|
|
||||||
validated_data['likedSongs'] = []
|
|
||||||
return Profile.objects.create(**validated_data) """
|
|
||||||
|
|
||||||
class TeamSerializer(serializers.ModelSerializer):
|
class TeamSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Team
|
model = Team
|
||||||
|
|
|
||||||
|
|
@ -8,61 +8,9 @@ from rest_framework.decorators import api_view, permission_classes, action
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||||
from rest_framework_simplejwt.exceptions import InvalidToken, AuthenticationFailed
|
from rest_framework_simplejwt.exceptions import InvalidToken, AuthenticationFailed
|
||||||
|
|
||||||
""" from .models import Profile, Team
|
|
||||||
from .serializers import ProfileSerializer, TeamSerializer """
|
|
||||||
|
|
||||||
from .models import Team
|
from .models import Team
|
||||||
from .serializers import TeamSerializer
|
from .serializers import TeamSerializer
|
||||||
|
|
||||||
""" class ProfileViewSet(viewsets.ViewSet):
|
|
||||||
queryset = Profile.objects.all()
|
|
||||||
def list(self, request):
|
|
||||||
queryset = Profile.objects.all()
|
|
||||||
serializer = ProfileSerializer(queryset, many=True)
|
|
||||||
return Response(serializer.data)
|
|
||||||
|
|
||||||
|
|
||||||
@action(detail=False, methods=['post'])
|
|
||||||
def AddSong(self, request):
|
|
||||||
if request.method == 'POST':
|
|
||||||
user = request.user.pk
|
|
||||||
new_song = request.data.get('new_song') # new_song should be a dict
|
|
||||||
profile = get_object_or_404(Profile, user=user)
|
|
||||||
serializer = ProfileSerializer(profile, data={'likedSongs': new_song}, partial=True)
|
|
||||||
if serializer.is_valid():
|
|
||||||
serializer.save()
|
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
@action(detail=False, methods=['post'])
|
|
||||||
def DeleteSong(self, request):
|
|
||||||
user = request.user.pk
|
|
||||||
song_id = request.data.get('id') # Assuming each song has a unique 'id' field
|
|
||||||
if not song_id:
|
|
||||||
return Response({'error': 'Song ID is required'}, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
profile = get_object_or_404(Profile, user=user)
|
|
||||||
if profile.likedSongs:
|
|
||||||
# Filter out the song with the given ID
|
|
||||||
updated_songs = [song for song in profile.likedSongs if song.get('id') != song_id]
|
|
||||||
profile.likedSongs = updated_songs
|
|
||||||
profile.save()
|
|
||||||
return Response({'message': 'Song deleted'}, status=status.HTTP_204_NO_CONTENT)
|
|
||||||
else:
|
|
||||||
return Response({'error': 'No songs to delete'}, status=status.HTTP_404_NOT_FOUND)
|
|
||||||
|
|
||||||
@action(
|
|
||||||
detail=False,
|
|
||||||
methods=['get'],
|
|
||||||
url_path='getlikedsongs/',
|
|
||||||
url_name='liked_songs',
|
|
||||||
)
|
|
||||||
def GetLikedSongs(request):
|
|
||||||
if request.method == 'GET':
|
|
||||||
user = request.user.pk
|
|
||||||
profile = get_object_or_404(Profile, user=user)
|
|
||||||
serializer = ProfileSerializer(profile)
|
|
||||||
return Response(serializer.data['likedSongs'], status=status.HTTP_200_OK) """
|
|
||||||
|
|
||||||
class TeamViewSet(ViewSet):
|
class TeamViewSet(ViewSet):
|
||||||
def list(self, request):
|
def list(self, request):
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
asgiref==3.8.1
backports.zoneinfo==0.2.1
certifi==2024.2.2
charset-normalizer==3.3.2
coreapi==2.3.3
coreschema==0.0.4
Django==4.1
django-coreapi==2.3.0
django-cors-headers==4.3.1
django-filter==24.2
djangorestframework==3.15.1
djangorestframework-simplejwt==5.3.1
idna==3.7
importlib_metadata==7.1.0
itypes==1.2.0
Jinja2==3.1.4
Markdown==3.6
MarkupSafe==2.1.5
pillow==10.3.0
psycopg2==2.9.9
PyJWT==2.8.0
pytz==2024.1
PyYAML==6.0.1
requests==2.32.2
sqlparse==0.5.0
typing_extensions==4.12.0
tzdata==2024.1
uritemplate==4.1.1
urllib3==1.26.15
zipp==3.19.0
|
asgiref==3.8.1
backports.zoneinfo==0.2.1
certifi==2024.2.2
charset-normalizer==3.3.2
coreapi==2.3.3
coreschema==0.0.4
django-coreapi==2.3.0
django-cors-headers==4.3.1
django-filter==24.2
djangorestframework==3.15.1
djangorestframework-simplejwt==5.3.1
idna==3.7
importlib_metadata==7.1.0
itypes==1.2.0
Jinja2==3.1.4
Markdown==3.6
MarkupSafe==2.1.5
pillow==10.3.0
PyJWT==2.8.0
pytz==2024.1
PyYAML==6.0.1
requests==2.32.2
sqlparse==0.5.0
typing_extensions==4.12.0
tzdata==2024.1
uritemplate==4.1.1
zipp==3.19.0
|
||||||
Loading…
Reference in New Issue