Init commit
This commit is contained in:
commit
3a1dbab9e2
23 changed files with 2314 additions and 0 deletions
179
.gitignore
vendored
Normal file
179
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
media/
|
||||
static/
|
||||
|
||||
*/migrations/*.py
|
||||
!*/migrations/__init__.py
|
||||
0
headphones/__init__.py
Normal file
0
headphones/__init__.py
Normal file
555
headphones/admin.py
Normal file
555
headphones/admin.py
Normal file
|
|
@ -0,0 +1,555 @@
|
|||
from django.contrib import admin
|
||||
from django import forms
|
||||
from django.urls import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.html import format_html
|
||||
|
||||
from .models import *
|
||||
|
||||
|
||||
class HeadphonesAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Headphones
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.instance and self.instance.pk:
|
||||
# Скрываем/показываем поля в зависимости от типа подключения
|
||||
if self.instance.connection_type == 'wired':
|
||||
self.fields.pop('wireless_details', None)
|
||||
self.fields.pop('hybrid_details', None)
|
||||
elif self.instance.connection_type == 'wireless':
|
||||
self.fields.pop('wired_details', None)
|
||||
self.fields.pop('hybrid_details', None)
|
||||
elif self.instance.connection_type == 'hybrid':
|
||||
self.fields.pop('wired_details', None)
|
||||
self.fields.pop('wireless_details', None)
|
||||
|
||||
|
||||
class HeadphonesConnectorInline(admin.TabularInline):
|
||||
model = HeadphonesConnector
|
||||
extra = 1
|
||||
fields = ('connector', 'is_primary', 'notes')
|
||||
fk_name = 'wired_headphones'
|
||||
|
||||
@admin.register(WiredHeadphones)
|
||||
|
||||
class WiredHeadphonesAdmin(admin.ModelAdmin):
|
||||
inlines = [HeadphonesConnectorInline]
|
||||
list_display = ('headphones', 'cable_connection_type', 'cable_length')
|
||||
list_filter = ('headphones__brand', 'cable_connection_type')
|
||||
search_fields = ('headphones__model',)
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super().get_queryset(request).select_related('headphones', 'cable_connection_type')
|
||||
|
||||
|
||||
class WiredHeadphonesInline(admin.StackedInline):
|
||||
model = WiredHeadphones
|
||||
extra = 0
|
||||
fieldsets = [
|
||||
(None, {
|
||||
'fields': [
|
||||
'cable_connection_type',
|
||||
'cable_length',
|
||||
'custom_connector_name'
|
||||
]
|
||||
})
|
||||
]
|
||||
|
||||
|
||||
class WirelessHeadphonesInline(admin.StackedInline):
|
||||
model = WirelessHeadphones
|
||||
extra = 0
|
||||
fieldsets = [
|
||||
(None, {
|
||||
'fields': [
|
||||
('wireless_technology', 'bluetooth_version'),
|
||||
('battery_life', 'charging_time'),
|
||||
'charging_interface',
|
||||
('has_charging_case', 'charging_case_battery_life'),
|
||||
'supported_codecs'
|
||||
]
|
||||
})
|
||||
]
|
||||
|
||||
|
||||
class HybridHeadphonesInline(admin.StackedInline):
|
||||
model = HybridHeadphones
|
||||
extra = 0
|
||||
fieldsets = [
|
||||
('Беспроводная часть', {
|
||||
'fields': [
|
||||
('wireless_technology', 'bluetooth_version'),
|
||||
('battery_life', 'charging_time'),
|
||||
'charging_interface',
|
||||
('has_charging_case', 'charging_case_battery_life'),
|
||||
'supported_codecs'
|
||||
]
|
||||
}),
|
||||
('Проводная часть', {
|
||||
'fields': [
|
||||
('cable_connection_type', 'connector_to_device'),
|
||||
'cable_length'
|
||||
]
|
||||
}),
|
||||
('Режимы работы', {
|
||||
'fields': [
|
||||
'auto_switch_mode',
|
||||
'simultaneous_connection'
|
||||
]
|
||||
})
|
||||
]
|
||||
|
||||
|
||||
class HeadphonesReviewInline(admin.StackedInline):
|
||||
model = HeadphonesReview
|
||||
extra = 1
|
||||
fields = ['resource', 'author', 'language', 'title', 'url', 'date']
|
||||
ordering = ['-date']
|
||||
autocomplete_fields = ['resource', 'author', 'language']
|
||||
|
||||
|
||||
class HeadphonesImagesInline(admin.TabularInline):
|
||||
model = HeadphonesImages
|
||||
extra = 1
|
||||
fields = ('photo', 'thumbnail_preview', 'admin_image_info', 'caption', 'is_main')
|
||||
readonly_fields = ('thumbnail_preview', 'admin_image_info')
|
||||
|
||||
def thumbnail_preview(self, obj):
|
||||
if obj.photo:
|
||||
return format_html('<img src="{}" style="max-height: 100px;"/>', obj.photo.url)
|
||||
return "-"
|
||||
|
||||
def admin_image_info(self, obj):
|
||||
return obj.image_info
|
||||
|
||||
admin_image_info.short_description = "Информация о изображении"
|
||||
admin_image_info.allow_tags = True
|
||||
|
||||
thumbnail_preview.short_description = "Превью"
|
||||
|
||||
|
||||
@admin.register(CaseMaterial)
|
||||
class CaseMaterialAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'slug')
|
||||
search_fields = ('name',)
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
list_per_page = 20
|
||||
|
||||
|
||||
class HeadphonesDriverInline(admin.TabularInline):
|
||||
model = HeadphonesDriver
|
||||
extra = 1
|
||||
fields = ('driver', 'count')
|
||||
autocomplete_fields = ['driver']
|
||||
|
||||
|
||||
@admin.register(Headphones)
|
||||
class HeadphonesAdmin(admin.ModelAdmin):
|
||||
form = HeadphonesAdminForm
|
||||
list_display = (
|
||||
'model', 'brand', 'connection_type', 'get_reviews_count', 'published')
|
||||
list_filter = ('brand', 'connection_type', 'published', 'case_type')
|
||||
search_fields = ('model', 'brand__name', 'description')
|
||||
prepopulated_fields = {'slug': ('brand', 'model')}
|
||||
filter_horizontal = ('tags', 'colors')
|
||||
list_editable = ('published',)
|
||||
actions = ['make_published', 'make_unpublished']
|
||||
readonly_fields = (
|
||||
'get_reviews_count_display',
|
||||
'date_added',
|
||||
'driver_configuration_display'
|
||||
)
|
||||
autocomplete_fields = ['case_material', 'noise_cancellation']
|
||||
|
||||
fieldsets = [
|
||||
('Основная информация', {
|
||||
'fields': [
|
||||
('brand', 'model'),
|
||||
'slug',
|
||||
'description',
|
||||
('connection_type', 'case_type', 'case_material'),
|
||||
('headphone_purpose', 'official_price'),
|
||||
'tags', # Добавляем теги в основную секцию
|
||||
]
|
||||
}),
|
||||
('Технические характеристики', {
|
||||
'fields': [
|
||||
('impedance', 'sensitivity'),
|
||||
'frequency_range',
|
||||
'frequency_response_chart',
|
||||
('microphone', 'noise_cancellation'),
|
||||
'ip_rating',
|
||||
]
|
||||
}),
|
||||
('Внешний вид', {
|
||||
'fields': [
|
||||
'colors',
|
||||
('weight', 'release_year')
|
||||
]
|
||||
}),
|
||||
('Метаданные', {
|
||||
'fields': [
|
||||
('date_added', 'published')
|
||||
],
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
]
|
||||
|
||||
inlines = [HeadphonesDriverInline, HeadphonesImagesInline]
|
||||
|
||||
# Методы для отображения
|
||||
def get_reviews_count(self, obj):
|
||||
return obj.reviews.count()
|
||||
|
||||
get_reviews_count.short_description = 'Кол-во обзоров'
|
||||
|
||||
def get_reviews_count_display(self, obj):
|
||||
count = self.get_reviews_count(obj)
|
||||
url = reverse('admin:headphones_headphonesreview_changelist') + f'?headphones__id__exact={obj.id}'
|
||||
return format_html('<a href="{}">{} обзоров</a>', url, count)
|
||||
|
||||
get_reviews_count_display.short_description = 'Обзоры'
|
||||
|
||||
def driver_configuration_display(self, obj):
|
||||
drivers = obj.headphonesdriver_set.select_related('driver__driver_type').all()
|
||||
return format_html("<br>".join(
|
||||
f"{d.driver.driver_type.name} {d.driver.size}mm × {d.count}"
|
||||
for d in drivers
|
||||
)) if drivers.exists() else "Не указано"
|
||||
|
||||
driver_configuration_display.short_description = 'Конфигурация драйверов'
|
||||
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
inlines = [
|
||||
HeadphonesDriverInline(self.model, self.admin_site),
|
||||
HeadphonesImagesInline(self.model, self.admin_site)
|
||||
]
|
||||
|
||||
if obj:
|
||||
if obj.connection_type == 'wired':
|
||||
inlines.append(WiredHeadphonesInline(self.model, self.admin_site))
|
||||
elif obj.connection_type == 'wireless':
|
||||
inlines.append(WirelessHeadphonesInline(self.model, self.admin_site))
|
||||
elif obj.connection_type == 'hybrid':
|
||||
inlines.append(HybridHeadphonesInline(self.model, self.admin_site))
|
||||
|
||||
inlines.append(HeadphonesReviewInline(self.model, self.admin_site))
|
||||
return inlines
|
||||
|
||||
|
||||
# Регистрация остальных моделей
|
||||
@admin.register(Brand)
|
||||
class BrandAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'country', 'year_founded', 'parent_company')
|
||||
list_filter = ('country',)
|
||||
search_fields = ('name',)
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
|
||||
@admin.register(Driver)
|
||||
class DriverAdmin(admin.ModelAdmin):
|
||||
list_display = ('driver_type', 'size', 'frequency_range')
|
||||
list_filter = ('driver_type',)
|
||||
search_fields = ('driver_type__name', 'size', 'frequency_range') # Добавлено
|
||||
autocomplete_fields = ['driver_type'] # Если нужно автозаполнение для типа
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super().get_queryset(request).select_related('driver_type')
|
||||
|
||||
|
||||
# ======================
|
||||
# Регистрация справочников
|
||||
# ======================
|
||||
|
||||
@admin.register(BrandCountry)
|
||||
class BrandCountryAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'slug')
|
||||
search_fields = ('name',)
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
|
||||
@admin.register(CaseColors)
|
||||
class CaseColorsAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'hex_code')
|
||||
search_fields = ('name',)
|
||||
|
||||
|
||||
@admin.register(CaseType)
|
||||
class CaseTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'slug')
|
||||
search_fields = ('name',)
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
|
||||
@admin.register(HeadphonePurpose)
|
||||
class HeadphonePurposeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'slug')
|
||||
search_fields = ('name',)
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
|
||||
@admin.register(TagCategory)
|
||||
class TagCategoryAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'slug')
|
||||
search_fields = ('name',)
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
|
||||
@admin.register(Tags)
|
||||
class TagsAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'category', 'slug')
|
||||
list_filter = ('category',)
|
||||
search_fields = ('name', 'category__name')
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
|
||||
# ======================
|
||||
# Регистрация аудио компонентов
|
||||
# ======================
|
||||
|
||||
@admin.register(DriverType)
|
||||
class DriverTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'slug')
|
||||
search_fields = ('name',)
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
|
||||
@admin.register(NoiseCancellationType)
|
||||
class NoiseCancellationTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'slug')
|
||||
search_fields = ('name',)
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
|
||||
@admin.register(CodecType)
|
||||
class CodecTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
search_fields = ('name',)
|
||||
|
||||
|
||||
@admin.register(AudioCodec)
|
||||
class AudioCodecAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'codec_type', 'max_bitrate', 'latency')
|
||||
list_filter = ('codec_type',)
|
||||
search_fields = ('name', 'description')
|
||||
filter_horizontal = ()
|
||||
|
||||
|
||||
# ======================
|
||||
# Регистрация разъемов и подключений
|
||||
# ======================
|
||||
|
||||
@admin.register(CableConnectionType)
|
||||
class CableConnectionTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'is_detachable')
|
||||
search_fields = ('name', 'description')
|
||||
readonly_fields = ('image_preview',)
|
||||
|
||||
def image_preview(self, obj):
|
||||
if obj.image:
|
||||
return mark_safe(f'<img src="{obj.image.url}" width="150" />')
|
||||
return "Нет изображения"
|
||||
|
||||
image_preview.short_description = 'Превью'
|
||||
|
||||
|
||||
@admin.register(DeviceConnectorType)
|
||||
class DeviceConnectorTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
search_fields = ('name', 'description')
|
||||
|
||||
|
||||
@admin.register(WirelessTechnology)
|
||||
class WirelessTechnologyAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
search_fields = ('name', 'description')
|
||||
|
||||
|
||||
@admin.register(ChargingInterface)
|
||||
class ChargingInterfaceAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
search_fields = ('name', 'description')
|
||||
|
||||
|
||||
# ======================
|
||||
# Регистрация отзывов и медиа
|
||||
# ======================
|
||||
|
||||
@admin.register(HeadphonesReviewType)
|
||||
class HeadphonesReviewTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'slug')
|
||||
search_fields = ('name',)
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
|
||||
@admin.register(HeadphonesReviewResource)
|
||||
class HeadphonesReviewResourceAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'review_type', 'website_link', 'slug')
|
||||
list_filter = ('type',)
|
||||
search_fields = ('name', 'type__name')
|
||||
|
||||
def website_link(self, obj):
|
||||
return format_html('<a href="{}" target="_blank">{}</a>', obj.url, obj.url)
|
||||
|
||||
website_link.short_description = "Ссылка на ресурс"
|
||||
|
||||
def review_type(self, obj):
|
||||
return obj.type.name if obj.type else '-'
|
||||
|
||||
review_type.short_description = 'Тип обзора'
|
||||
review_type.admin_order_field = 'type__name'
|
||||
|
||||
|
||||
@admin.register(HeadphonesConnector)
|
||||
class HeadphonesConnectorAdmin(admin.ModelAdmin):
|
||||
list_display = ('headphones', 'connector_display', 'is_primary', 'notes')
|
||||
list_filter = ('is_primary', 'connector')
|
||||
|
||||
def connector_display(self, obj):
|
||||
return str(obj.connector)
|
||||
|
||||
connector_display.short_description = 'Разъем'
|
||||
connector_display.admin_order_field = 'connector__name'
|
||||
|
||||
|
||||
@admin.register(HeadphonesReviewLanguage)
|
||||
class HeadphonesReviewLanguageAdmin(admin.ModelAdmin):
|
||||
list_display = ('language',)
|
||||
search_fields = ('language',)
|
||||
|
||||
|
||||
@admin.register(HeadphonesReviewAuthor)
|
||||
class HeadphonesReviewAuthorAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'nickname', 'reviews_count')
|
||||
search_fields = ('name', 'nickname')
|
||||
filter_horizontal = ('resources',)
|
||||
|
||||
def reviews_count(self, obj):
|
||||
return obj.headphonesreview_set.count()
|
||||
|
||||
reviews_count.short_description = 'Кол-во обзоров'
|
||||
|
||||
# Добавьте инлайн для отображения обзоров автора
|
||||
class ReviewsInline(admin.TabularInline):
|
||||
model = HeadphonesReview
|
||||
extra = 0
|
||||
fields = ('title', 'headphones', 'date')
|
||||
readonly_fields = ('title', 'headphones', 'date')
|
||||
|
||||
inlines = [ReviewsInline]
|
||||
|
||||
|
||||
class HeadphonesReviewAuthorInline(admin.StackedInline):
|
||||
model = HeadphonesReviewAuthor
|
||||
extra = 1
|
||||
fields = ('name', 'nickname', 'resources')
|
||||
filter_horizontal = ('resources',)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@admin.register(HeadphonesReview)
|
||||
class HeadphonesReviewAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
'title',
|
||||
'headphones_link',
|
||||
'resource_link',
|
||||
'date',
|
||||
# 'author_display',
|
||||
'review_link'
|
||||
)
|
||||
list_filter = (
|
||||
'resource__type',
|
||||
'headphones__brand',
|
||||
'date'
|
||||
)
|
||||
search_fields = (
|
||||
'title',
|
||||
'headphones__model',
|
||||
# 'author',
|
||||
'resource__name'
|
||||
)
|
||||
raw_id_fields = ('headphones', 'resource')
|
||||
date_hierarchy = 'date'
|
||||
list_select_related = ('headphones', 'resource')
|
||||
|
||||
def headphones_link(self, obj):
|
||||
if obj.headphones:
|
||||
url = reverse('admin:headphones_headphones_change', args=[obj.headphones.id])
|
||||
return format_html('<a href="{}">{}</a>', url, obj.headphones.model)
|
||||
return "-"
|
||||
|
||||
headphones_link.short_description = "Наушники"
|
||||
headphones_link.admin_order_field = 'headphones__model'
|
||||
|
||||
def resource_link(self, obj):
|
||||
url = reverse('admin:headphones_headphonesreviewresource_change', args=[obj.resource.id])
|
||||
return format_html('<a href="{}">{}</a>', url, obj.resource.name)
|
||||
|
||||
resource_link.short_description = "Ресурс"
|
||||
resource_link.admin_order_field = 'resource__name'
|
||||
|
||||
def review_link(self, obj):
|
||||
return format_html('<a href="{}" target="_blank">Открыть</a>', obj.url)
|
||||
|
||||
review_link.short_description = "Ссылка"
|
||||
|
||||
def author_display(self, obj):
|
||||
if obj.author:
|
||||
url = reverse('admin:headphones_headphonesreviewauthor_change', args=[obj.author.id])
|
||||
return format_html('<a href="{}">{}</a>', url, obj.author.name)
|
||||
return "-"
|
||||
|
||||
author_display.short_description = "Автор"
|
||||
|
||||
|
||||
@admin.register(HeadphonesImages)
|
||||
class HeadphonesImagesAdmin(admin.ModelAdmin):
|
||||
list_display = ('product', 'caption')
|
||||
list_filter = ('product__brand',)
|
||||
search_fields = ('product__model', 'caption')
|
||||
readonly_fields = ('image_preview',)
|
||||
|
||||
def image_preview(self, obj):
|
||||
if obj.photo:
|
||||
return mark_safe(f'<img src="{obj.photo.url}" width="150" />')
|
||||
return "Нет изображения"
|
||||
|
||||
image_preview.short_description = 'Превью'
|
||||
|
||||
|
||||
# ======================
|
||||
# Регистрация вспомогательных моделей
|
||||
# ======================
|
||||
|
||||
@admin.register(HeadphonesDriver)
|
||||
class HeadphonesDriverAdmin(admin.ModelAdmin):
|
||||
list_display = ('headphones', 'driver_type', 'size', 'count')
|
||||
list_filter = ('driver__driver_type',)
|
||||
|
||||
def driver_type(self, obj):
|
||||
return obj.driver.driver_type.name
|
||||
|
||||
driver_type.short_description = 'Тип драйвера'
|
||||
|
||||
def size(self, obj):
|
||||
return f"{obj.driver.size}mm" if obj.driver.size else '-'
|
||||
|
||||
size.short_description = 'Размер'
|
||||
|
||||
|
||||
@admin.register(Comment)
|
||||
class CommentAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'post', 'date', 'short_body')
|
||||
list_filter = ('date', 'user')
|
||||
# search_fields = ('body', 'post__model', 'user__username')
|
||||
readonly_fields = ('date',)
|
||||
|
||||
def short_body(self, obj):
|
||||
return obj.body[:50] + '...' if len(obj.body) > 50 else obj.body
|
||||
|
||||
short_body.short_description = 'Текст'
|
||||
6
headphones/apps.py
Normal file
6
headphones/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class HeadphonesConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'headphones'
|
||||
0
headphones/migrations/__init__.py
Normal file
0
headphones/migrations/__init__.py
Normal file
1050
headphones/models.py
Normal file
1050
headphones/models.py
Normal file
File diff suppressed because it is too large
Load diff
3
headphones/tests.py
Normal file
3
headphones/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
headphones/views.py
Normal file
3
headphones/views.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
0
headphones_backend/__init__.py
Normal file
0
headphones_backend/__init__.py
Normal file
16
headphones_backend/asgi.py
Normal file
16
headphones_backend/asgi.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
ASGI config for headphones_backend project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'headphones_backend.settings')
|
||||
|
||||
application = get_asgi_application()
|
||||
144
headphones_backend/settings.py
Normal file
144
headphones_backend/settings.py
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
"""
|
||||
Django settings for headphones_backend project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 5.2.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.2/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/5.2/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-8ig5m_im8c%+f_$8px^yas!qh=#j@e)ul(y3cg%6&jzb#cn0ab'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'rest_framework',
|
||||
'imagekit',
|
||||
'headphones',
|
||||
'users.apps.UsersConfig',
|
||||
# 'headphones_main.apps.HeadphonesMainConfig',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'headphones_backend.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [BASE_DIR / 'templates']
|
||||
,
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'headphones_backend.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"OPTIONS": {
|
||||
"service": "headphones",
|
||||
"passfile": ".headphones_db",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.AllowAny',
|
||||
],
|
||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||
'PAGE_SIZE': 10
|
||||
}
|
||||
|
||||
AUTH_USER_MODEL = 'users.User'
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/5.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'ru-ru'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/5.2/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
26
headphones_backend/urls.py
Normal file
26
headphones_backend/urls.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
"""
|
||||
URL configuration for headphones_backend project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/5.2/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
16
headphones_backend/wsgi.py
Normal file
16
headphones_backend/wsgi.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
WSGI config for headphones_backend project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'headphones_backend.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
22
manage.py
Executable file
22
manage.py
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'headphones_backend.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
249
poetry.lock
generated
Normal file
249
poetry.lock
generated
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.8.1"
|
||||
description = "ASGI specs, helper code, and adapters"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"},
|
||||
{file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "5.2"
|
||||
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "Django-5.2-py3-none-any.whl", hash = "sha256:91ceed4e3a6db5aedced65e3c8f963118ea9ba753fc620831c77074e620e7d83"},
|
||||
{file = "Django-5.2.tar.gz", hash = "sha256:1a47f7a7a3d43ce64570d350e008d2949abe8c7e21737b351b6a1611277c6d89"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
asgiref = ">=3.8.1"
|
||||
sqlparse = ">=0.3.1"
|
||||
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
|
||||
[package.extras]
|
||||
argon2 = ["argon2-cffi (>=19.1.0)"]
|
||||
bcrypt = ["bcrypt"]
|
||||
|
||||
[[package]]
|
||||
name = "django-appconf"
|
||||
version = "1.1.0"
|
||||
description = "A helper class for handling configuration defaults of packaged apps gracefully."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "django-appconf-1.1.0.tar.gz", hash = "sha256:9fcead372f82a0f21ee189434e7ae9c007cbb29af1118c18251720f3d06243e4"},
|
||||
{file = "django_appconf-1.1.0-py3-none-any.whl", hash = "sha256:7abd5a163ff57557f216e84d3ce9dac36c37ffce1ab9a044d3d53b7c943dd10f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django = "*"
|
||||
|
||||
[[package]]
|
||||
name = "django-imagekit"
|
||||
version = "5.0.0"
|
||||
description = "Automated image processing for Django models."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "django-imagekit-5.0.0.tar.gz", hash = "sha256:aae9f74a8e9b6ceb5d15f7d8e266302901e76d9f532c78bd5135cb0fa206a6b0"},
|
||||
{file = "django_imagekit-5.0.0-py3-none-any.whl", hash = "sha256:a8e77ed6549751026a51f961bb2cd5fda739be691496da8eecbe68ffb966c261"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django-appconf = "*"
|
||||
pilkit = "*"
|
||||
|
||||
[package.extras]
|
||||
async = ["django-celery (>=3.0)"]
|
||||
async-dramatiq = ["django-dramatiq (>=0.4.0)"]
|
||||
async-rq = ["django-rq (>=0.6.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "djangorestframework"
|
||||
version = "3.16.0"
|
||||
description = "Web APIs for Django, made easy."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "djangorestframework-3.16.0-py3-none-any.whl", hash = "sha256:bea7e9f6b96a8584c5224bfb2e4348dfb3f8b5e34edbecb98da258e892089361"},
|
||||
{file = "djangorestframework-3.16.0.tar.gz", hash = "sha256:f022ff46613584de994c0c6a4aebbace5fd700555fbe9d33b865ebf173eba6c9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django = ">=4.2"
|
||||
|
||||
[[package]]
|
||||
name = "pilkit"
|
||||
version = "3.0"
|
||||
description = "A collection of utilities and processors for the Python Imaging Library."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pilkit-3.0-py3-none-any.whl", hash = "sha256:fe1707b0411a1d0cbf9ad3986779fa5a346cec4582a188740924aa39f504d117"},
|
||||
{file = "pilkit-3.0.tar.gz", hash = "sha256:f6719e8cc0482e5447f5cb94f18b949d8e604ea9673a9b019c74d41b779e4eab"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
Pillow = ">=7.0"
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "11.1.0"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-win32.whl", hash = "sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a"},
|
||||
{file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90"},
|
||||
{file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb"},
|
||||
{file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442"},
|
||||
{file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83"},
|
||||
{file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f"},
|
||||
{file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73"},
|
||||
{file = "pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0"},
|
||||
{file = "pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
|
||||
fpx = ["olefile"]
|
||||
mic = ["olefile"]
|
||||
tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"]
|
||||
typing = ["typing-extensions ; python_version < \"3.10\""]
|
||||
xmp = ["defusedxml"]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg"
|
||||
version = "3.2.6"
|
||||
description = "PostgreSQL database adapter for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "psycopg-3.2.6-py3-none-any.whl", hash = "sha256:f3ff5488525890abb0566c429146add66b329e20d6d4835662b920cbbf90ac58"},
|
||||
{file = "psycopg-3.2.6.tar.gz", hash = "sha256:16fa094efa2698f260f2af74f3710f781e4a6f226efe9d1fd0c37f384639ed8a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
|
||||
[package.extras]
|
||||
binary = ["psycopg-binary (==3.2.6) ; implementation_name != \"pypy\""]
|
||||
c = ["psycopg-c (==3.2.6) ; implementation_name != \"pypy\""]
|
||||
dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "isort-psycopg", "isort[colors] (>=6.0)", "mypy (>=1.14)", "pre-commit (>=4.0.1)", "types-setuptools (>=57.4)", "wheel (>=0.37)"]
|
||||
docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
pool = ["psycopg-pool"]
|
||||
test = ["anyio (>=4.0)", "mypy (>=1.14)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"]
|
||||
|
||||
[[package]]
|
||||
name = "sqlparse"
|
||||
version = "0.5.3"
|
||||
description = "A non-validating SQL parser."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca"},
|
||||
{file = "sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["build", "hatch"]
|
||||
doc = ["sphinx"]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.2"
|
||||
description = "Provider of IANA time zone data"
|
||||
optional = false
|
||||
python-versions = ">=2"
|
||||
groups = ["main"]
|
||||
markers = "sys_platform == \"win32\""
|
||||
files = [
|
||||
{file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"},
|
||||
{file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.13"
|
||||
content-hash = "04a0cf20da59a089ce0256eef5326fcf4b928120ee38f3cbdef93ff118269b0d"
|
||||
21
pyproject.toml
Normal file
21
pyproject.toml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
[project]
|
||||
name = "headphones-backend"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = [
|
||||
{name = "apheyhys",email = "apheyhys@gmail.com"}
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"django (>=5.2,<6.0)",
|
||||
"psycopg (>=3.2.6,<4.0.0)",
|
||||
"pillow (>=11.1.0,<12.0.0)",
|
||||
"django-imagekit (>=5.0.0,<6.0.0)",
|
||||
"djangorestframework (>=3.16.0,<4.0.0)"
|
||||
]
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
0
users/__init__.py
Normal file
0
users/__init__.py
Normal file
3
users/admin.py
Normal file
3
users/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
users/apps.py
Normal file
6
users/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UsersConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'users'
|
||||
0
users/migrations/__init__.py
Normal file
0
users/migrations/__init__.py
Normal file
9
users/models.py
Normal file
9
users/models.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from django.contrib.auth.models import AbstractUser
|
||||
from django.db import models
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
photo = models.CharField(max_length=350, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
3
users/tests.py
Normal file
3
users/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
users/views.py
Normal file
3
users/views.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
Loading…
Add table
Reference in a new issue