Compare commits
102 Commits
304764f9b7
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f79af33222 | ||
|
|
a4813f95bb | ||
|
|
c397dbb61b | ||
|
|
aaa62aed1d | ||
|
|
3e75202d4e | ||
|
|
0fde709f0f | ||
|
|
34ddb78434 | ||
|
|
b182d82f11 | ||
|
|
4ff7fc3ec4 | ||
|
|
5440518578 | ||
|
|
63305124d9 | ||
|
|
db99642278 | ||
|
|
aabd9ce429 | ||
|
|
2af168fe7e | ||
|
|
a7c3ba3f14 | ||
|
|
79b94a2faf | ||
|
|
6e17c6c30c | ||
|
|
d51a646ad2 | ||
|
|
74d5eeccf7 | ||
|
|
0e7c813e18 | ||
|
|
19cfd6fc60 | ||
|
|
d54aacf4dd | ||
|
|
7ffe48b414 | ||
|
|
f261aab9a9 | ||
|
|
cd0028f457 | ||
|
|
0bc330bab4 | ||
|
|
fb0d36f060 | ||
|
|
ad8bcf0f19 | ||
|
|
a8b8388c8c | ||
|
|
260c772d5a | ||
|
|
e7fdec247f | ||
|
|
d4492aa3ed | ||
|
|
d3a228557d | ||
|
|
76ef618cb9 | ||
|
|
365f1ea020 | ||
|
|
004299556f | ||
|
|
ab1c3a4ab7 | ||
|
|
d21f2d4dc4 | ||
|
|
0b537ed1c1 | ||
|
|
b045c793ee | ||
|
|
e5365b31ec | ||
|
|
898063767e | ||
|
|
34f6a33562 | ||
|
|
5ac0bdc32d | ||
|
|
338f50fe98 | ||
|
|
410cfa53b2 | ||
|
|
e4ffc837db | ||
|
|
67304065cb | ||
|
|
e7a0f8fca3 | ||
|
|
2f4394d7a1 | ||
|
|
30e055f9d8 | ||
|
|
923356f497 | ||
|
|
c0ab73212b | ||
|
|
6bdb3f6d91 | ||
|
|
fdd9b7c6da | ||
|
|
24aa7f6d18 | ||
|
|
f38cf6ce0f | ||
|
|
17729bce41 | ||
|
|
318afa3382 | ||
|
|
d901d3d25b | ||
|
|
48b77061ef | ||
|
|
222af6ec43 | ||
|
|
a099d4aa8d | ||
|
|
1549d85377 | ||
|
|
8b42b83fa1 | ||
|
|
c7b8c0f766 | ||
|
|
aca78e69ba | ||
|
|
56671a2075 | ||
|
|
78ae14169a | ||
|
|
aed34bcf54 | ||
|
|
9e8c6bd1b7 | ||
|
|
4c6638bb18 | ||
|
|
14ac6229d3 | ||
|
|
ad3d470195 | ||
|
|
75922bec90 | ||
|
|
53e939cc3b | ||
|
|
b5987004ed | ||
|
|
50d26ba2d0 | ||
|
|
c9dde8d76b | ||
|
|
176f3eea6f | ||
|
|
676b4b9b20 | ||
|
|
0b1c89da53 | ||
|
|
b863e9c9b1 | ||
|
|
71653c20c7 | ||
|
|
b7b7e66177 | ||
|
|
9fdf1ea8aa | ||
|
|
36f0dedc26 | ||
|
|
389b5ff90a | ||
|
|
22728403ee | ||
|
|
0f122f0201 | ||
|
|
bb38587719 | ||
|
|
4e5c6911bf | ||
|
|
9402b625e7 | ||
|
|
ae2f82a93b | ||
|
|
86f82cbefe | ||
|
|
363c60c382 | ||
|
|
4fadb033e2 | ||
|
|
208428c5c7 | ||
|
|
2f8fa42152 | ||
|
|
24840697e7 | ||
|
|
d2ac3b89bd | ||
|
|
8806ad8c60 |
@@ -5,9 +5,9 @@ name: default
|
||||
steps:
|
||||
- name: build
|
||||
commands:
|
||||
- docker build --no-cache -t izaac:latest .
|
||||
- docker tag izaac:latest registry.izaac.pl:5000/izaac:latest
|
||||
- docker push registry.izaac.pl:5000/izaac:latest
|
||||
- docker build --no-cache -t izaac-master:latest .
|
||||
- docker tag izaac-master:latest registry.knck.pl:5000/izaac-master:latest
|
||||
- docker push registry.knck.pl:5000/izaac-master:latest
|
||||
|
||||
- name: delete
|
||||
environment:
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
FROM python:alpine
|
||||
FROM python:3.12-alpine
|
||||
|
||||
WORKDIR /usr/scr/app
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
ENV REACT_CLIENT_ID="rrgNLSEqlcl45RVMQa0LxQLOHOgZ2L7ZlGpXUrCp"
|
||||
ENV REACT_CLIENT_SECRET="lKsmraeSmihY8rTQgd8TTGIncUloW7XokR8WLI0oZq8jCJ59guAkopg9ZOUeXFdyrqwuLi6TyXLZJDp1KB8DWEus05ttS3NQHoLvfRoOQtJpQaxzyIo85oykjQn25Keb"
|
||||
RUN pip install --upgrade pip
|
||||
COPY ./requirements.txt .
|
||||
RUN apk update && apk add postgresql-dev gcc python3-dev musl-dev
|
||||
RUN pip install psycopg2
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
ENV DJANGO_SETTINGS_MODULE=izaac.settings
|
||||
|
||||
342
Untitled-1.json
Normal file
342
Untitled-1.json
Normal file
@@ -0,0 +1,342 @@
|
||||
[
|
||||
{
|
||||
"posting_option": "S",
|
||||
"company_name": "Global Solutions Ltd.",
|
||||
"name": "System Analyst",
|
||||
"content": "<h1>Quality Assurance</h1><p>Join our team at Innovatech as a UI/UX Designer. We are looking for a motivated individual with experience in software development. This position is located in Paris.</p>",
|
||||
"min_salary": 43789,
|
||||
"max_salary": 90334,
|
||||
"localization": "Toronto",
|
||||
"experience_level": "A",
|
||||
"employment_type": "B2B",
|
||||
"work_from_home": "wfh",
|
||||
"skill_levels": [
|
||||
{
|
||||
"skill_id": "38",
|
||||
"skill_level": "M"
|
||||
},
|
||||
{
|
||||
"skill_id": "21",
|
||||
"skill_level": "E"
|
||||
},
|
||||
{
|
||||
"skill_id": "13",
|
||||
"skill_level": "B"
|
||||
}
|
||||
],
|
||||
"contact_email": "info@companyemail.com",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"category": "B",
|
||||
"webpage": "https://www.companywebsite.com",
|
||||
"image": "https://www.companywebsite.com/image.jpg",
|
||||
"require_salary": False,
|
||||
"vat_number": "123456789"
|
||||
},
|
||||
{
|
||||
"posting_option": "S",
|
||||
"company_name": "Tech Pioneers",
|
||||
"name": "Project Manager",
|
||||
"content": "<h1>Project Manager</h1><p>Join our team at Innovatech as a Senior Developer. We are looking for a motivated individual with experience in software development. This position is located in London.</p>",
|
||||
"min_salary": 42524,
|
||||
"max_salary": 102194,
|
||||
"localization": "New York",
|
||||
"experience_level": "B",
|
||||
"employment_type": "FT",
|
||||
"work_from_home": "off",
|
||||
"skill_levels": [
|
||||
{
|
||||
"skill_id": "28",
|
||||
"skill_level": "E"
|
||||
},
|
||||
{
|
||||
"skill_id": "7",
|
||||
"skill_level": "M"
|
||||
},
|
||||
{
|
||||
"skill_id": "1",
|
||||
"skill_level": "B"
|
||||
}
|
||||
],
|
||||
"contact_email": "info@companyemail.com",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"category": "C",
|
||||
"webpage": "https://www.companywebsite.com",
|
||||
"image": "https://www.companywebsite.com/image.jpg",
|
||||
"require_salary": False,
|
||||
"vat_number": "123456789"
|
||||
},
|
||||
{
|
||||
"posting_option": "S",
|
||||
"company_name": "Digitally Inspired",
|
||||
"name": "Senior Developer",
|
||||
"content": "<h1>System Analyst</h1><p>Join our team at Innovatech as a DevOps Engineer. We are looking for a motivated individual with experience in software development. This position is located in Paris.</p>",
|
||||
"min_salary": 40657,
|
||||
"max_salary": 107155,
|
||||
"localization": "Toronto",
|
||||
"experience_level": "C",
|
||||
"employment_type": "FT",
|
||||
"work_from_home": "off",
|
||||
"skill_levels": [
|
||||
{
|
||||
"skill_id": "4",
|
||||
"skill_level": "B"
|
||||
},
|
||||
{
|
||||
"skill_id": "30",
|
||||
"skill_level": "M"
|
||||
},
|
||||
{
|
||||
"skill_id": "3",
|
||||
"skill_level": "B"
|
||||
}
|
||||
],
|
||||
"contact_email": "info@companyemail.com",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"category": "D",
|
||||
"webpage": "https://www.companywebsite.com",
|
||||
"image": "https://www.companywebsite.com/image.jpg",
|
||||
"require_salary": False,
|
||||
"vat_number": "123456789"
|
||||
},
|
||||
{
|
||||
"posting_option": "S",
|
||||
"company_name": "Tech Innovations Inc.",
|
||||
"name": "Product Manager",
|
||||
"content": "<h1>DevOps Engineer</h1><p>Join our team at Creative Minds LLC as a DevOps Engineer. We are looking for a motivated individual with experience in software development. This position is located in Singapore.</p>",
|
||||
"min_salary": 56868,
|
||||
"max_salary": 101327,
|
||||
"localization": "Berlin",
|
||||
"experience_level": "C",
|
||||
"employment_type": "INT",
|
||||
"work_from_home": "wfh",
|
||||
"skill_levels": [
|
||||
{
|
||||
"skill_id": "28",
|
||||
"skill_level": "E"
|
||||
},
|
||||
{
|
||||
"skill_id": "1",
|
||||
"skill_level": "E"
|
||||
},
|
||||
{
|
||||
"skill_id": "4",
|
||||
"skill_level": "E"
|
||||
}
|
||||
],
|
||||
"contact_email": "info@companyemail.com",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"category": "E",
|
||||
"webpage": "https://www.companywebsite.com",
|
||||
"image": "https://www.companywebsite.com/image.jpg",
|
||||
"require_salary": False,
|
||||
"vat_number": "123456789"
|
||||
},
|
||||
{
|
||||
"posting_option": "S",
|
||||
"company_name": "Global Solutions Ltd.",
|
||||
"name": "Project Manager",
|
||||
"content": "<h1>Project Manager</h1><p>Join our team at Bright Future Tech as a DevOps Engineer. We are looking for a motivated individual with experience in software development. This position is located in Singapore.</p>",
|
||||
"min_salary": 62093,
|
||||
"max_salary": 98690,
|
||||
"localization": "London",
|
||||
"experience_level": "D",
|
||||
"employment_type": "FT",
|
||||
"work_from_home": "wfh",
|
||||
"skill_levels": [
|
||||
{
|
||||
"skill_id": "30",
|
||||
"skill_level": "M"
|
||||
},
|
||||
{
|
||||
"skill_id": "33",
|
||||
"skill_level": "E"
|
||||
},
|
||||
{
|
||||
"skill_id": "2",
|
||||
"skill_level": "B"
|
||||
}
|
||||
],
|
||||
"contact_email": "info@companyemail.com",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"category": "F",
|
||||
"webpage": "https://www.companywebsite.com",
|
||||
"image": "https://www.companywebsite.com/image.jpg",
|
||||
"require_salary": False,
|
||||
"vat_number": "123456789"
|
||||
},
|
||||
{
|
||||
"posting_option": "S",
|
||||
"company_name": "CodeCrafters",
|
||||
"name": "Data Scientist",
|
||||
"content": "<h1>Senior Developer</h1><p>Join our team at Tech Innovations Inc. as a Software Engineer. We are looking for a motivated individual with experience in software development. This position is located in San Francisco.</p>",
|
||||
"min_salary": 50492,
|
||||
"max_salary": 70097,
|
||||
"localization": "Sydney",
|
||||
"experience_level": "F",
|
||||
"employment_type": "MC",
|
||||
"work_from_home": "hyb",
|
||||
"skill_levels": [
|
||||
{
|
||||
"skill_id": "7",
|
||||
"skill_level": "M"
|
||||
},
|
||||
{
|
||||
"skill_id": "27",
|
||||
"skill_level": "E"
|
||||
},
|
||||
{
|
||||
"skill_id": "34",
|
||||
"skill_level": "B"
|
||||
}
|
||||
],
|
||||
"contact_email": "info@companyemail.com",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"category": "G",
|
||||
"webpage": "https://www.companywebsite.com",
|
||||
"image": "https://www.companywebsite.com/image.jpg",
|
||||
"require_salary": False,
|
||||
"vat_number": "123456789"
|
||||
},
|
||||
{
|
||||
"posting_option": "S",
|
||||
"company_name": "Digitally Inspired",
|
||||
"name": "UI/UX Designer",
|
||||
"content": "<h1>DevOps Engineer</h1><p>Join our team at Digitally Inspired as a Product Manager. We are looking for a motivated individual with experience in software development. This position is located in Berlin.</p>",
|
||||
"min_salary": 59399,
|
||||
"max_salary": 110461,
|
||||
"localization": "Sydney",
|
||||
"experience_level": "E",
|
||||
"employment_type": "CW",
|
||||
"work_from_home": "hyb",
|
||||
"skill_levels": [
|
||||
{
|
||||
"skill_id": "19",
|
||||
"skill_level": "E"
|
||||
},
|
||||
{
|
||||
"skill_id": "13",
|
||||
"skill_level": "E"
|
||||
},
|
||||
{
|
||||
"skill_id": "36",
|
||||
"skill_level": "B"
|
||||
}
|
||||
],
|
||||
"contact_email": "info@companyemail.com",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"category": "H",
|
||||
"webpage": "https://www.companywebsite.com",
|
||||
"image": "https://www.companywebsite.com/image.jpg",
|
||||
"require_salary": False,
|
||||
"vat_number": "123456789"
|
||||
},
|
||||
{
|
||||
"posting_option": "S",
|
||||
"company_name": "CodeCrafters",
|
||||
"name": "Project Manager",
|
||||
"content": "<h1>System Analyst</h1><p>Join our team at Digitally Inspired as a Data Scientist. We are looking for a motivated individual with experience in software development. This position is located in Sydney.</p>",
|
||||
"min_salary": 62307,
|
||||
"max_salary": 103344,
|
||||
"localization": "Sydney",
|
||||
"experience_level": "C",
|
||||
"employment_type": "B2B",
|
||||
"work_from_home": "wfh",
|
||||
"skill_levels": [
|
||||
{
|
||||
"skill_id": "14",
|
||||
"skill_level": "M"
|
||||
},
|
||||
{
|
||||
"skill_id": "5",
|
||||
"skill_level": "M"
|
||||
},
|
||||
{
|
||||
"skill_id": "15",
|
||||
"skill_level": "E"
|
||||
}
|
||||
],
|
||||
"contact_email": "info@companyemail.com",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"category": "J",
|
||||
"webpage": "https://www.companywebsite.com",
|
||||
"image": "https://www.companywebsite.com/image.jpg",
|
||||
"require_salary": False,
|
||||
"vat_number": "123456789"
|
||||
},
|
||||
{
|
||||
"posting_option": "S",
|
||||
"company_name": "CodeCrafters",
|
||||
"name": "DevOps Engineer",
|
||||
"content": "<h1>System Analyst</h1><p>Join our team at Tech Pioneers as a System Analyst. We are looking for a motivated individual with experience in software development. This position is located in Paris.</p>",
|
||||
"min_salary": 52962,
|
||||
"max_salary": 107338,
|
||||
"localization": "Toronto",
|
||||
"experience_level": "C",
|
||||
"employment_type": "B2B",
|
||||
"work_from_home": "wfh",
|
||||
"skill_levels": [
|
||||
{
|
||||
"skill_id": "11",
|
||||
"skill_level": "M"
|
||||
},
|
||||
{
|
||||
"skill_id": "31",
|
||||
"skill_level": "B"
|
||||
},
|
||||
{
|
||||
"skill_id": "29",
|
||||
"skill_level": "M"
|
||||
}
|
||||
],
|
||||
"contact_email": "info@companyemail.com",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"category": "Z",
|
||||
"webpage": "https://www.companywebsite.com",
|
||||
"image": "https://www.companywebsite.com/image.jpg",
|
||||
"require_salary": False,
|
||||
"vat_number": "123456789"
|
||||
},
|
||||
{
|
||||
"posting_option": "S",
|
||||
"company_name": "CodeCrafters",
|
||||
"name": "Project Manager",
|
||||
"content": "<h1>Quality Assurance</h1><p>Join our team at Future Horizons as a UI/UX Designer. We are looking for a motivated individual with experience in software development. This position is located in Singapore.</p>",
|
||||
"min_salary": 48580,
|
||||
"max_salary": 104717,
|
||||
"localization": "Singapore",
|
||||
"experience_level": "G",
|
||||
"employment_type": "FT",
|
||||
"work_from_home": "off",
|
||||
"skill_levels": [
|
||||
{
|
||||
"skill_id": "16",
|
||||
"skill_level": "E"
|
||||
},
|
||||
{
|
||||
"skill_id": "14",
|
||||
"skill_level": "B"
|
||||
},
|
||||
{
|
||||
"skill_id": "8",
|
||||
"skill_level": "B"
|
||||
}
|
||||
],
|
||||
"contact_email": "info@companyemail.com",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"category": "A",
|
||||
"webpage": "https://www.companywebsite.com",
|
||||
"image": "https://www.companywebsite.com/image.jpg",
|
||||
"require_salary": False,
|
||||
"vat_number": "123456789"
|
||||
}
|
||||
]
|
||||
@@ -1,4 +1,17 @@
|
||||
from django.contrib import admin
|
||||
from .models import MyUser
|
||||
from .models import MyUser, AnonymousUserData
|
||||
# Register your models here.
|
||||
admin.site.register(MyUser)
|
||||
|
||||
class MyUserAdmin(admin.ModelAdmin):
|
||||
list_display = ('email', 'first_name', 'last_name')
|
||||
search_fields = ('email', 'first_name', 'last_name')
|
||||
ordering = ['email']
|
||||
|
||||
admin.site.register(MyUser, MyUserAdmin)
|
||||
|
||||
class AnonymousUserDataAdmin(admin.ModelAdmin):
|
||||
list_display = ('contact_email', 'first_name', 'last_name')
|
||||
search_fields = ('contact_email', 'first_name', 'last_name')
|
||||
ordering = ['contact_email']
|
||||
|
||||
admin.site.register(AnonymousUserData, AnonymousUserDataAdmin)
|
||||
@@ -33,6 +33,7 @@ class MyUser(AbstractBaseUser, PermissionsMixin):
|
||||
is_staff = models.BooleanField(_('staff status'), default=False)
|
||||
is_active = models.BooleanField(_('active'), default=True)
|
||||
company_name = models.CharField(_('company name'), max_length=100)
|
||||
is_company = models.BooleanField(_('company'), default=False)
|
||||
|
||||
objects = MyUserManager()
|
||||
USERNAME_FIELD = 'username'
|
||||
|
||||
@@ -14,12 +14,12 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: izaac-backend
|
||||
image: registry.izaac.pl:5000/izaac:latest
|
||||
image: registry.knck.pl:5000/izaac-master:latest
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
volumeMounts:
|
||||
- name: static-storage
|
||||
mountPath: /usr/scr/app/staticfiles # Ścieżka, gdzie Django oczekuje plików statycznych
|
||||
mountPath: /usr/scr/app/staticfiles
|
||||
- name: static-media
|
||||
mountPath: /usr/scr/app/media
|
||||
volumes:
|
||||
|
||||
@@ -17,6 +17,9 @@ import os
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
REACT_CLIENT_ID = os.environ.get("REACT_CLIENT_ID")
|
||||
|
||||
REACT_CLIENT_SECRET = os.environ.get("REACT_CLIENT_SECRET")
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
|
||||
@@ -27,9 +30,16 @@ SECRET_KEY = "django-insecure-ztiqj957k(j*@ms=-^9j+=j7wd+cyo*+9g&hkfh1bji-@#&6(e
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ['izaac.izaac.pl', 'localhost', '127.0.0.1',]
|
||||
ALLOWED_HOSTS = ['https://izaac.knck.pl', 'izaac.knck.pl', 'localhost', 'izaac-backend', 'izaac-frontend']
|
||||
|
||||
CORS_ALLOW_ALL_ORIGINS = True
|
||||
CORS_ALLOWED_ORIGNS = [
|
||||
"https://izaac.knck.pl",
|
||||
"http://izaac.knck.pl",
|
||||
"http://izaac-frontend",
|
||||
"http://izaac-backend",
|
||||
]
|
||||
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
|
||||
# CORS_ALLOWED_ORIGIN = [
|
||||
# "http://localhost:8000",
|
||||
@@ -42,15 +52,16 @@ CORS_ALLOW_ALL_ORIGINS = True
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
],
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
OAUTH2_PROVIDER = {
|
||||
# this is the list of available scopes
|
||||
'SCOPES': {'read': 'Read scope', 'write': 'Write scope', 'groups': 'Access to your groups'}
|
||||
'SCOPES': {'read': 'Read scope', 'write': 'Write scope', 'main': 'Access to all'}
|
||||
}
|
||||
|
||||
# Application definition
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
from django.contrib import admin
|
||||
from .models import SkillLevels, JobListing, CompanyLogo
|
||||
from .models import JobOffer, CompanyLogo, Skill
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(SkillLevels)
|
||||
admin.site.register(JobListing)
|
||||
admin.site.register(CompanyLogo)
|
||||
|
||||
class JobOfferAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'company_name', 'created_at', 'status', 'status_paid', 'expiration_date')
|
||||
list_filter = ('status', 'status_paid', 'expiration_date')
|
||||
search_fields = ('name', 'company_name', 'content')
|
||||
date_hierarchy = 'created_at'
|
||||
ordering = ['status', 'created_at']
|
||||
|
||||
class CompanyLogoAdmin(admin.ModelAdmin):
|
||||
list_display = ('company_name', 'company_logo')
|
||||
search_fields = ('company_name',)
|
||||
ordering = ['company_name']
|
||||
|
||||
class SkillAdmin(admin.ModelAdmin):
|
||||
list_display = ('skill_name',)
|
||||
search_fields = ('skill_name',)
|
||||
ordering = ['skill_name']
|
||||
|
||||
admin.site.register(JobOffer, JobOfferAdmin,)
|
||||
admin.site.register(CompanyLogo, CompanyLogoAdmin)
|
||||
admin.site.register(Skill, SkillAdmin)
|
||||
|
||||
23
jobposting/auth.py
Normal file
23
jobposting/auth.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from oauth2_provider.contrib.rest_framework import OAuth2Authentication
|
||||
from oauth2_provider.models import Application
|
||||
from oauth2_provider.oauth2_backends import get_oauthlib_core
|
||||
|
||||
|
||||
class OAuth2ClientCredentialAuthentication(OAuth2Authentication):
|
||||
|
||||
def authenticate(self, request):
|
||||
authentication = super().authenticate(request)
|
||||
|
||||
if authentication is not None and not self.is_client_credential_request(authentication):
|
||||
return authentication
|
||||
|
||||
if self.is_client_credential_request(authentication):
|
||||
access_token = authentication[1]
|
||||
user = access_token.application.user
|
||||
return user, access_token
|
||||
|
||||
return None
|
||||
|
||||
def is_client_credential_request(self, authentication):
|
||||
access_token = authentication[1]
|
||||
return access_token.application.authorization_grant_type == Application.GRANT_CLIENT_CREDENTIALS
|
||||
@@ -7,44 +7,88 @@ from datetime import timedelta
|
||||
|
||||
from .helpers import rename_file
|
||||
|
||||
class JobListing(models.Model):
|
||||
class JobOffer(models.Model):
|
||||
|
||||
posting_options = [
|
||||
('M', 'Minimal'),
|
||||
('S', 'Standard'),
|
||||
('P', 'Premium'),
|
||||
]
|
||||
|
||||
status_choices = [
|
||||
('A', 'Aktywna'),
|
||||
('C', 'Zakończona'),
|
||||
('W', 'Oczekująca na sprawdzenie'),
|
||||
('R', 'Zarchiwizowana'),
|
||||
]
|
||||
|
||||
paid_status = [
|
||||
('P', 'Oplacona'),
|
||||
('N', 'Nieoplacona'),
|
||||
('D', 'Inny status')
|
||||
]
|
||||
|
||||
category_choices = [
|
||||
('A', 'Budownictwo'),
|
||||
('B', 'IT'),
|
||||
('C', 'Elektryka i Elektronika'),
|
||||
('D', 'Produkcja'),
|
||||
('E', 'Mechanika i konstrukcje'),
|
||||
('F', 'Chemia i Biotechnologia'),
|
||||
('G', 'Biomedyczne'),
|
||||
('H', 'Automatyka i Robotyka'),
|
||||
('I', 'Logistyka i Transport'),
|
||||
('J', 'Sprzedaż'),
|
||||
('Z', 'Inne')
|
||||
]
|
||||
|
||||
experience_levels = [
|
||||
('A', 'Stażysta'),
|
||||
('B', 'Junior'),
|
||||
('C', 'Mid'),
|
||||
('D', 'Senior'),
|
||||
('E', 'Lead'),
|
||||
('F', 'Manager'),
|
||||
('G', 'Inne'),
|
||||
]
|
||||
|
||||
work_from_home = [
|
||||
('wfh', 'Praca zdalna'),
|
||||
('hyb', 'Hybrydowa'),
|
||||
('off', 'Stacjonarna'),
|
||||
]
|
||||
|
||||
employment_types = [
|
||||
('B2B', 'Kontrakt B2B'),
|
||||
('FT', 'Umowa o pracę'),
|
||||
('MC', 'Umowa zlecenie'),
|
||||
('CW', 'Umowa o dzieło'),
|
||||
('INT', 'Staż'),
|
||||
]
|
||||
|
||||
posting_option = models.CharField(max_length=1, choices=posting_options)
|
||||
company_name = models.CharField(max_length=255)
|
||||
name = models.CharField(max_length=255)
|
||||
content = models.TextField()
|
||||
minsalary = models.IntegerField()
|
||||
maxsalary = models.IntegerField()
|
||||
requiresalary = models.BooleanField(null=True, default=False)
|
||||
min_salary = models.IntegerField()
|
||||
max_salary = models.IntegerField()
|
||||
require_salary = models.BooleanField(null=True, default=False)
|
||||
webpage = models.CharField(max_length=255)
|
||||
localization = models.CharField(max_length=255)
|
||||
vat_number = models.CharField(max_length=10)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
status = models.CharField(max_length=1, choices=status_choices, default='W')
|
||||
status_paid = models.CharField(max_length=1, choices=paid_status, default='N')
|
||||
# category = models.CharField(max_length=255, null=True, blank=True)
|
||||
status_paid = models.CharField(max_length=1, choices=paid_status,
|
||||
default='N')
|
||||
category = models.CharField(max_length=1, choices=category_choices,
|
||||
default='Z')
|
||||
expiration_date = models.DateTimeField(null=True, blank=True)
|
||||
image = models.CharField(max_length=255, null=True, blank=True)
|
||||
experience_level = models.CharField(max_length=255, null=True, blank=True)
|
||||
employmentType = models.CharField(max_length=255, null=True, blank=True)
|
||||
workFromHome = models.CharField(max_length=255)
|
||||
experience_level = models.CharField(max_length=255, choices=experience_levels, default='G')
|
||||
employment_type = models.CharField(max_length=3, choices=employment_types, default='FT')
|
||||
work_from_home = models.CharField(max_length=3, choices=work_from_home, default='off')
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
# skillLevels = models.ManyToManyField('SkillLevels', blank=True)
|
||||
skill_levels = models.JSONField(null=True, blank=True, default=list)
|
||||
anonymous_user_data = models.ForeignKey(AnonymousUserData,
|
||||
null=True,
|
||||
blank=True,
|
||||
@@ -65,25 +109,13 @@ class JobListing(models.Model):
|
||||
def __str__(self):
|
||||
return f"{self.name} at {self.company_name}"
|
||||
|
||||
|
||||
class Skill(models.Model):
|
||||
skill_name = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.skill_name}"
|
||||
|
||||
class SkillLevels(models.Model):
|
||||
|
||||
proficiency_choices = [
|
||||
('N', 'Nice to have'),
|
||||
('B', 'Podstawowy'),
|
||||
('M', 'Średnio zaawansowany'),
|
||||
('A', 'Zaawansowany'),
|
||||
('E', 'Ekspert'),
|
||||
]
|
||||
|
||||
skill_id = models.ForeignKey(Skill, on_delete=models.CASCADE)
|
||||
skill_level = models.CharField(max_length=1, choices=proficiency_choices,)
|
||||
jobposting_id = models.ForeignKey(JobListing, on_delete=models.CASCADE)
|
||||
|
||||
class CompanyLogo(models.Model):
|
||||
company_logo = models.ImageField(upload_to=rename_file)
|
||||
|
||||
@@ -5,6 +5,7 @@ class ClientCredentialPermission(BasePermission):
|
||||
if request.auth is None:
|
||||
return False
|
||||
grant_type = request.auth.application.get_authorization_grant_type_display()
|
||||
print(grant_type)
|
||||
if request.user is None and grant_type == 'Client credentials':
|
||||
return True
|
||||
else:
|
||||
|
||||
@@ -1,48 +1,61 @@
|
||||
from rest_framework import serializers
|
||||
from jobposting.models import JobListing, SkillLevels, CompanyLogo, Skill
|
||||
from jobposting.models import JobOffer, CompanyLogo, Skill
|
||||
from core.models import MyUser, AnonymousUserData
|
||||
|
||||
|
||||
class MyUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = MyUser
|
||||
fields = ['id', 'email', 'first_name', 'last_name']
|
||||
|
||||
|
||||
class AnonymousUserDataSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AnonymousUserData
|
||||
fields = ['id', 'contact_email', 'first_name', 'last_name']
|
||||
|
||||
|
||||
class SkillLevelsSerializer(serializers.ModelSerializer):
|
||||
skill_id = serializers.SlugRelatedField(
|
||||
slug_field='skill_name',
|
||||
queryset=Skill.objects.all(),
|
||||
)
|
||||
# jobposting_id = serializers.PrimaryKeyRelatedField(source='jobposting.id', queryset=JobListing.objects.all())
|
||||
class Meta:
|
||||
model = SkillLevels
|
||||
fields = ['skill_id', 'skill_level', 'jobposting_id']
|
||||
|
||||
class JobListingSerializer(serializers.ModelSerializer):
|
||||
class JobOfferSerializer(serializers.ModelSerializer):
|
||||
created_by = MyUserSerializer(read_only=True)
|
||||
anonymous_user_data = AnonymousUserDataSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = JobListing
|
||||
model = JobOffer
|
||||
fields = [
|
||||
'id', 'posting_option',
|
||||
'company_name', 'name', 'content', 'minsalary', 'maxsalary',
|
||||
'localization', 'created_by', 'created_at', 'status', 'status_paid',
|
||||
'expiration_date', 'experience_level', 'employmentType', 'workFromHome',
|
||||
'updated_at', 'contact_email', 'first_name', 'last_name', 'anonymous_user_data'
|
||||
'company_name', 'name', 'content', 'min_salary', 'max_salary',
|
||||
'localization', 'created_by', 'created_at', 'status',
|
||||
'status_paid', 'expiration_date', 'experience_level',
|
||||
'employment_type', 'work_from_home', 'updated_at', 'skill_levels',
|
||||
'contact_email', 'first_name', 'last_name', 'anonymous_user_data',
|
||||
'category', 'webpage', 'image', 'require_salary', 'vat_number'
|
||||
]
|
||||
|
||||
|
||||
class CompanyLogoSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = CompanyLogo
|
||||
fields = ['id', 'company_name','company_logo']
|
||||
|
||||
|
||||
class SkillSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Skill
|
||||
fields = ['id', 'skill_name']
|
||||
|
||||
class SlimJobOffersSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = JobOffer
|
||||
fields = ['id', 'company_name', 'name', 'min_salary', 'max_salary',
|
||||
'localization', 'image', 'posting_option', 'category',
|
||||
'require_salary' ]
|
||||
|
||||
class MainJobOfferSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = JobOffer
|
||||
fields = [
|
||||
'id', 'company_name', 'name', 'content',
|
||||
'localization', 'expiration_date', 'experience_level',
|
||||
'employment_type', 'work_from_home', 'skill_levels',
|
||||
'contact_email', 'category', 'webpage',
|
||||
]
|
||||
@@ -5,14 +5,20 @@ from jobposting import views
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'users', views.MyUserViewSet)
|
||||
router.register(r'joblistings', views.JobListingViewSet)
|
||||
router.register(r'skilllevels', views.SkillLevelsViewSet)
|
||||
router.register(r'joblistings', views.JobOfferViewSet)
|
||||
router.register(r'companylogo', views.CompanyLogoViewSet)
|
||||
router.register(r'skills', views.SkillViewset)
|
||||
|
||||
app_name = 'jobposting'
|
||||
|
||||
# Twoje urlpatterns
|
||||
urlpatterns = [
|
||||
# ...
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
path('', include(router.urls)), # test only
|
||||
path('skill/', views.SkillMainView.as_view(), name='skill-list'),
|
||||
path('skill/<int:pk>', views.SkillMainView.as_view(), name='skill-list'),
|
||||
path('joboffers/<int:pk>/', views.JobOfferDetailView.as_view(),
|
||||
name='joboffer-detail'),
|
||||
path('joboffers/', views.JobOfferCreateView.as_view(),
|
||||
name='joboffer-create'),
|
||||
path('joboffers_list/', views.JobOfferListView.as_view(),
|
||||
name='joboffer-list'),
|
||||
]
|
||||
@@ -1,32 +1,68 @@
|
||||
from rest_framework import viewsets, permissions
|
||||
from rest_framework import viewsets, permissions, authentication
|
||||
from rest_framework.response import Response
|
||||
from jobposting.models import JobListing, SkillLevels, CompanyLogo, Skill
|
||||
from jobposting.models import JobOffer, CompanyLogo, Skill
|
||||
from core.models import MyUser, AnonymousUserData
|
||||
from rest_framework import status
|
||||
from jobposting.permissions import ClientCredentialPermission
|
||||
from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope
|
||||
|
||||
from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope, OAuth2Authentication
|
||||
from .auth import OAuth2ClientCredentialAuthentication
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
from jobposting.serializers import (
|
||||
JobListingSerializer,
|
||||
SkillLevelsSerializer,
|
||||
JobOfferSerializer,
|
||||
MyUserSerializer,
|
||||
CompanyLogoSerializer,
|
||||
SkillSerializer
|
||||
SkillSerializer,
|
||||
SlimJobOffersSerializer,
|
||||
MainJobOfferSerializer
|
||||
)
|
||||
|
||||
def remove_none_from_filters(filter: int|str, filter_type: str) -> int|str|None:
|
||||
if filter_type == 'int':
|
||||
if filter is not None or filter != '':
|
||||
try:
|
||||
filter = int(filter)
|
||||
except ValueError:
|
||||
filter = None
|
||||
except TypeError:
|
||||
filter = None
|
||||
else:
|
||||
filter = None
|
||||
else:
|
||||
if filter is not None or filter != '':
|
||||
pass
|
||||
else:
|
||||
filter = None
|
||||
|
||||
print(filter) # test only
|
||||
|
||||
return filter
|
||||
|
||||
|
||||
class MyUserViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [ClientCredentialPermission] # test only
|
||||
# authentication_classes = [OAuth2Authentication, authentication.SessionAuthentication]
|
||||
# permission_classes = [permissions.IsAuthenticated|TokenHasReadWriteScope]
|
||||
queryset = MyUser.objects.all()
|
||||
serializer_class = MyUserSerializer
|
||||
# permission_classes = [permissions.IsAuthenticated]
|
||||
required_scope = ['main']
|
||||
authentication_classes = [] ### test only - to be changed
|
||||
permission_classes = [] ###
|
||||
# # permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
class JobListingViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [ClientCredentialPermission] # test only
|
||||
queryset = JobListing.objects.all()
|
||||
serializer_class = JobListingSerializer
|
||||
# permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
||||
|
||||
class JobOfferViewSet(viewsets.ModelViewSet):
|
||||
# authentication_classes = [OAuth2Authentication, authentication.SessionAuthentication]
|
||||
# permission_classes = [permissions.IsAuthenticated|TokenHasReadWriteScope] # test only
|
||||
queryset = JobOffer.objects.all()
|
||||
serializer_class = JobOfferSerializer
|
||||
required_scope = ['main']
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
# # permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
||||
|
||||
def perform_create(self, serializer):
|
||||
if self.request.user.is_authenticated:
|
||||
@@ -46,30 +82,165 @@ class JobListingViewSet(viewsets.ModelViewSet):
|
||||
|
||||
serializer.save(anonymous_user_data=anonymous_user_data)
|
||||
|
||||
class SkillLevelsViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [ClientCredentialPermission] # test only
|
||||
queryset = SkillLevels.objects.all()
|
||||
serializer_class = SkillLevelsSerializer
|
||||
# permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
data = request.data
|
||||
print(type(data))
|
||||
if isinstance(data, list): # Sprawdzenie, czy dane to lista
|
||||
serializers = [self.get_serializer(data=item) for item in data]
|
||||
for serializer in serializers:
|
||||
serializer.is_valid(raise_exception=True)
|
||||
self.perform_create(serializer)
|
||||
return Response([serializer.data for serializer in serializers], status=status.HTTP_201_CREATED)
|
||||
else:
|
||||
return super().create(request, *args, **kwargs)
|
||||
|
||||
class CompanyLogoViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [ClientCredentialPermission]
|
||||
# authentication_classes = [OAuth2Authentication, authentication.SessionAuthentication]
|
||||
# permission_classes = [permissions.IsAuthenticated|TokenHasReadWriteScope]
|
||||
queryset = CompanyLogo.objects.all()
|
||||
serializer_class = CompanyLogoSerializer
|
||||
required_scope = ['main']
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
|
||||
class JobOfferListView(APIView):
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
|
||||
|
||||
def get(self, request, format=None):
|
||||
dict_request = dict(request.query_params)
|
||||
print(dict_request)
|
||||
categories = dict_request.get('categories[]', None)
|
||||
max_salary = remove_none_from_filters(
|
||||
request.query_params.get('max_salary', None), 'int')
|
||||
min_salary = remove_none_from_filters(
|
||||
request.query_params.get('min_salary', None), 'int')
|
||||
localization = remove_none_from_filters(
|
||||
request.query_params.get('localization', None), 'str')
|
||||
name = remove_none_from_filters(
|
||||
request.query_params.get('name', None), 'str')
|
||||
|
||||
filters = {
|
||||
'name__icontains': name,
|
||||
'min_salary__gte': min_salary,
|
||||
'max_salary__lte': max_salary,
|
||||
'localization': localization,
|
||||
'category__in': categories,
|
||||
'experience_level': request.query_params.get('experience_level', None),
|
||||
'employment_type': request.query_params.get('employment_type', None),
|
||||
'work_from_home': request.query_params.get('work_from_home', None),
|
||||
}
|
||||
|
||||
# remove None values
|
||||
filters = {k: v for k, v in filters.items() if v is not None}
|
||||
print(filters)
|
||||
job_offers = JobOffer.objects.filter(**filters)
|
||||
serializer = SlimJobOffersSerializer(job_offers, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class SkillViewset(viewsets.ModelViewSet):
|
||||
permission_classes = [ClientCredentialPermission]
|
||||
# authentication_classes = [OAuth2Authentication, authentication.SessionAuthentication]
|
||||
# permission_classes = [permissions.IsAuthenticated|TokenHasReadWriteScope]
|
||||
queryset = Skill.objects.all()
|
||||
serializer_class = SkillSerializer
|
||||
required_scope = ['main']
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
|
||||
class SkillMainView(APIView):
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def get_object(self, pk):
|
||||
try:
|
||||
return Skill.objects.get(pk=pk)
|
||||
except Skill.DoesNotExist:
|
||||
return Response({'error': 'Not Found'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def get(self, request, pk=None, format=None):
|
||||
if pk:
|
||||
skill = self.get_object(pk)
|
||||
serializer = SkillSerializer(skill)
|
||||
else:
|
||||
skill = Skill.objects.all()
|
||||
serializer = SkillSerializer(skill, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
def post(self, request, format=None):
|
||||
serializer = SkillSerializer(data=request.data)
|
||||
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)
|
||||
|
||||
def put(self, request, pk, format=None):
|
||||
skill = self.get_object(pk)
|
||||
serializer = SkillSerializer(skill, data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def delete(self, request, pk, format=None):
|
||||
skill = self.get_object(pk)
|
||||
skill.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class JobOfferDetailView(APIView):
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def get_object(self, pk):
|
||||
try:
|
||||
return JobOffer.objects.get(pk=pk)
|
||||
except JobOffer.DoesNotExist:
|
||||
return Response({'error': 'Not Found'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def get(self, request, pk, format=None):
|
||||
job_offer = self.get_object(pk)
|
||||
serializer = MainJobOfferSerializer(job_offer,)
|
||||
return Response(serializer.data)
|
||||
|
||||
def put(self, request, pk, format=None):
|
||||
job_offer = self.get_object(pk)
|
||||
serializer = JobOfferSerializer(job_offer, data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def delete(self, request, pk, format=None):
|
||||
job_listing = self.get_object(pk)
|
||||
job_listing.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def patch(self, request, pk, format=None):
|
||||
job_offer = self.get_object(pk)
|
||||
serializer = JobOfferSerializer(job_offer, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
class JobOfferCreateView(APIView):
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def post(self, request, format=None):
|
||||
print(request.META)
|
||||
serializer = JobOfferSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
if request.user.is_authenticated:
|
||||
serializer.save(created_by=request.user)
|
||||
else:
|
||||
# Pobieranie danych z formularza
|
||||
first_name = request.data.get('first_name')
|
||||
last_name = request.data.get('last_name')
|
||||
contact_email = request.data.get('contact_email')
|
||||
|
||||
# Tworzenie rekordu AnonymousUserData
|
||||
anonymous_user_data = AnonymousUserData.objects.create(
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
contact_email=contact_email
|
||||
)
|
||||
|
||||
serializer.save(anonymous_user_data=anonymous_user_data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
django
|
||||
gunicorn
|
||||
djangorestframework
|
||||
psycopg2-binary
|
||||
django-allauth
|
||||
dj-rest-auth
|
||||
django-cors-headers
|
||||
|
||||
Reference in New Issue
Block a user