Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion acme_project/acme_project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
'django.contrib.staticfiles',
'birthday.apps.BirthdayConfig',
'pages.apps.PagesConfig',
'users.apps.UsersConfig',
'django_bootstrap5',
]

MIDDLEWARE = [
Expand Down Expand Up @@ -79,10 +81,20 @@

USE_I18N = True

USE_L10N = True
USE_L10N = False

USE_TZ = True

STATIC_URL = '/static/'

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

AUTH_USER_MODEL = 'users.MyUser'

EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'

EMAIL_FILE_PATH = BASE_DIR / 'sent_emails'

LOGIN_REDIRECT_URL = 'pages:homepage'

LOGIN_URL = 'login'
24 changes: 22 additions & 2 deletions acme_project/acme_project/urls.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
# Импортируем настройки проекта.
from django.conf import settings
# Импортируем функцию, позволяющую серверу разработки отдавать файлы.
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
from django.urls import include, path, reverse_lazy
from django.contrib.auth.forms import UserCreationForm
from django.views.generic.edit import CreateView
from users.forms import CustomUserCreationForm

from django.contrib.auth import get_user_model
User = get_user_model()

urlpatterns = [
path('', include('pages.urls')),
path('admin/', admin.site.urls),
path('birthday/', include('birthday.urls')),
]
path('auth/', include('django.contrib.auth.urls')),
path(
'auth/registration/',
CreateView.as_view(
template_name='registration/registration_form.html',
form_class=CustomUserCreationForm,
success_url=reverse_lazy('pages:homepage'),
),
name='registration',
),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
4 changes: 4 additions & 0 deletions acme_project/birthday/admin.py
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
from django.contrib import admin

from .models import Birthday

admin.site.register(Birthday)
45 changes: 45 additions & 0 deletions acme_project/birthday/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from django import forms

from .models import Birthday
from .validators import real_age
from django.core.exceptions import ValidationError
from django.core.mail import send_mail

BEATLES = {'Джон Леннон', 'Пол Маккартни', 'Джордж Харрисон', 'Ринго Старр'}


class BirthdayForm(forms.ModelForm):
class Meta:
model = Birthday
fields = '__all__'
widgets = {
'birthday': forms.DateInput(attrs={'type': 'date'}),
}
validators=(real_age,),

def clean_first_name(self):
# Получаем значение имени из словаря очищенных данных.
first_name = self.cleaned_data['first_name']
# Разбиваем полученную строку по пробелам
# и возвращаем только первое имя.
return first_name.split()[0]

def clean(self):
# Получаем имя и фамилию из очищенных полей формы.
super().clean()
first_name = self.cleaned_data['first_name']
last_name = self.cleaned_data['last_name']
# Проверяем вхождение сочетания имени и фамилии во множество имён.
if f'{first_name} {last_name}' in BEATLES:
# Отправляем письмо, если кто-то представляется
# именем одного из участников Beatles.
send_mail(
subject='Another Beatles member',
message=f'{first_name} {last_name} пытался опубликовать запись!',
from_email='[email protected]',
recipient_list=['[email protected]'],
fail_silently=True,
)
raise ValidationError(
'Мы тоже любим Битлз, но введите, пожалуйста, настоящее имя!'
)
23 changes: 23 additions & 0 deletions acme_project/birthday/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.16 on 2023-11-30 14:04

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Birthday',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=20, verbose_name='Имя')),
('last_name', models.CharField(blank=True, help_text='Необязательное поле', max_length=20, verbose_name='Фамилия')),
('birthday', models.DateField(verbose_name='Дата рождения')),
],
),
]
23 changes: 23 additions & 0 deletions acme_project/birthday/migrations/0002_auto_20231217_2300.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.16 on 2023-12-17 20:00

import birthday.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('birthday', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='birthday',
name='birthday',
field=models.DateField(validators=[birthday.validators.real_age], verbose_name='Дата рождения'),
),
migrations.AddConstraint(
model_name='birthday',
constraint=models.UniqueConstraint(fields=('first_name', 'last_name', 'birthday'), name='Unique person constraint'),
),
]
18 changes: 18 additions & 0 deletions acme_project/birthday/migrations/0003_birthday_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.16 on 2023-12-17 20:19

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('birthday', '0002_auto_20231217_2300'),
]

operations = [
migrations.AddField(
model_name='birthday',
name='image',
field=models.ImageField(blank=True, upload_to='birthdays_images', verbose_name='Фото'),
),
]
25 changes: 25 additions & 0 deletions acme_project/birthday/models.py
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
from django.db import models

from .validators import real_age

from django.urls import reverse


class Birthday(models.Model):
first_name = models.CharField('Имя', max_length=20)
last_name = models.CharField(
'Фамилия', blank=True, help_text='Необязательное поле', max_length=20
)
birthday = models.DateField('Дата рождения', validators=(real_age,))
image = models.ImageField('Фото', upload_to='birthdays_images', blank=True)

class Meta:
constraints = (
models.UniqueConstraint(
fields=('first_name', 'last_name', 'birthday'),
name='Unique person constraint',
),
)

def get_absolute_url(self):
# С помощью функции reverse() возвращаем URL объекта.
return reverse('birthday:detail', kwargs={'pk': self.pk})
6 changes: 5 additions & 1 deletion acme_project/birthday/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@
app_name = 'birthday'

urlpatterns = [
path('', views.birthday, name='create'),
path('', views.BirthdayCreateView.as_view(), name='create'),
path('list/', views.BirthdayListView.as_view(), name='list'),
path('<int:pk>/', views.BirthdayDetailView.as_view(), name='detail'),
path('<int:pk>/edit/', views.BirthdayUpdateView.as_view(), name='edit'),
path('<int:pk>/delete/', views.BirthdayDeleteView.as_view(), name='delete'),
]
47 changes: 47 additions & 0 deletions acme_project/birthday/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# birthday/utils.py
# Импортируем модуль для работы с датами.
from datetime import date


def calculate_birthday_countdown(birthday):
"""
Возвращает количество дней до следующего дня рождения.

Если день рождения сегодня, то возвращает 0.
"""
# Сохраняем текущую дату в переменную today.
today = date.today()
# Получаем день рождения в этом году
# с помощью вспомогательной функции (ниже).
this_year_birthday = get_birthday_for_year(birthday, today.year)

# Если день рождения уже прошёл...
if this_year_birthday < today:
# ...то следующий ДР будет в следующем году.
next_birthday = get_birthday_for_year(birthday, today.year + 1)
else:
# А если в этом году ещё не было ДР, то он и будет следующим.
next_birthday = this_year_birthday

# Считаем разницу между следующим днём рождения и сегодняшним днём в днях.
birthday_countdown = (next_birthday - today).days
return birthday_countdown


def get_birthday_for_year(birthday, year):
"""
Получает дату дня рождения для конкретного года.

Ошибка ValueError возможна только в случае
с високосными годами и ДР 29 февраля.
В этом случае приравниваем дату ДР к 1 марта.
"""
try:
# Пробуем заменить год в дате рождения на переданный в функцию.
calculated_birthday = birthday.replace(year=year)
# Если возникла ошибка, значит, день рождения 29 февраля
# и подставляемый год не является високосным.
except ValueError:
# В этом случае устанавливаем ДР 1 марта.
calculated_birthday = date(year=year, month=3, day=1)
return calculated_birthday
18 changes: 18 additions & 0 deletions acme_project/birthday/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# birthday/validators.py
# Импортируем класс для работы с датами.
from datetime import date

# Импортируем ошибку валидации.
from django.core.exceptions import ValidationError


# На вход функция будет принимать дату рождения.
def real_age(value: date) -> None:
# Считаем разницу между сегодняшним днём и днём рождения в днях
# и делим на 365.
age = (date.today() - value).days / 365
# Если возраст меньше 1 года или больше 120 лет — выбрасываем ошибку валидации.
if age < 1 or age > 120:
raise ValidationError(
'Ожидается возраст от 1 года до 120 лет'
)
42 changes: 38 additions & 4 deletions acme_project/birthday/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,40 @@
from django.shortcuts import render
from django.views.generic import (
CreateView, DeleteView, DetailView, ListView, UpdateView
)
from django.urls import reverse_lazy

from .forms import BirthdayForm
from .models import Birthday
from .utils import calculate_birthday_countdown

def birthday(request):
context = {}
return render(request, 'birthday/birthday.html', context=context)

class BirthdayListView(ListView):
model = Birthday
ordering = 'id'
paginate_by = 10


class BirthdayCreateView(CreateView):
model = Birthday
form_class = BirthdayForm


class BirthdayUpdateView(UpdateView):
model = Birthday
form_class = BirthdayForm


class BirthdayDeleteView(DeleteView):
model = Birthday
success_url = reverse_lazy('birthday:list')


class BirthdayDetailView(DetailView):
model = Birthday

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['birthday_countdown'] = calculate_birthday_countdown(
self.object.birthday
)
return context
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion acme_project/pages/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
app_name = 'pages'

urlpatterns = [
path('', views.homepage, name='homepage'),
path('', views.HomePage.as_view(), name='homepage'),
]
20 changes: 17 additions & 3 deletions acme_project/pages/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
from django.shortcuts import render
# Импортируем класс TemplateView, чтобы унаследоваться от него.
from django.views.generic import TemplateView

from birthday.models import Birthday

def homepage(request):
return render(request, 'pages/index.html')

class HomePage(TemplateView):
# В атрибуте template_name обязательно указывается имя шаблона,
# на основе которого будет создана возвращаемая страница.
template_name = 'pages/index.html'

def get_context_data(self, **kwargs):
# Получаем словарь контекста из родительского метода.
context = super().get_context_data(**kwargs)
# Добавляем в словарь ключ total_count;
# значение ключа — число объектов модели Birthday.
context['total_count'] = Birthday.objects.count()
# Возвращаем изменённый словарь контекста.
return context
4 changes: 2 additions & 2 deletions acme_project/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<head>
<meta charset="UTF-8">
<title>Проект ACME</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
{% load django_bootstrap5 %}
{% bootstrap_css %}
</head>
<body>
{% include "includes/header.html" %}
Expand Down
5 changes: 0 additions & 5 deletions acme_project/templates/birthday/birthday.html

This file was deleted.

Loading