Commit 7e1bde5a authored by Давид Ли's avatar Давид Ли

lesson 55

parent 06ec603a
File added
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from accounts.models import User
from accounts.models import User, Profile
# Register your models here.
admin.site.register(User)
class ProfileInline(admin.StackedInline):
model = Profile
fields = ['birth_date', 'avatar']
class UserProfileAdmin(UserAdmin):
inlines = [ProfileInline]
admin.site.register(User, UserProfileAdmin)
from django import forms
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.contrib.auth import password_validation
from django.contrib.auth import password_validation, get_user_model
from accounts.models import User
from accounts.models import User, Profile
class LoginForm(AuthenticationForm):
......@@ -60,3 +60,70 @@ class RegisterForm(UserCreationForm):
'last_name': forms.TextInput(attrs={'class': 'form-control mb-3'}),
'email': forms.EmailInput(attrs={'class': 'form-control mb-3'}),
}
class UserUpdateForm(forms.ModelForm):
class Meta:
model = get_user_model()
fields = ['first_name', 'last_name', 'email']
labels = {'first_name': 'Имя', 'last_name': 'Фамилия', 'email': 'Почта'}
widgets = {
'first_name': forms.TextInput(attrs={'class': 'form-control mb-3'}),
'last_name': forms.TextInput(attrs={'class': 'form-control mb-3'}),
'email': forms.EmailInput(attrs={'class': 'form-control mb-3'}),
}
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = Profile
exclude = ['user']
widgets = {
'birth_date': forms.SelectDateWidget(attrs={'class': 'form-control mb-3'}),
'avatar': forms.ClearableFileInput(attrs={'class': 'form-control mb-3'}),
}
class PasswordChangeForm(forms.ModelForm):
old_password = forms.CharField(
label='Old password',
strip=False,
widget=forms.PasswordInput(attrs={'class': 'form-control mb-3'})
)
password = forms.CharField(
label='New password',
strip=False,
widget=forms.PasswordInput(attrs={'class': 'form-control mb-3'})
)
password_confirm = forms.CharField(
label='Confirm password',
strip=False,
widget=forms.PasswordInput(attrs={'class': 'form-control mb-3'})
)
def clean_password_confirm(self):
password = self.cleaned_data.get('password')
password_confirm = self.cleaned_data.get('password_confirm')
if password and password_confirm and password_confirm == password:
return password_confirm
raise forms.ValidationError('Passwords not match or missed')
def clean_old_password(self):
old_password = self.cleaned_data.get('old_password')
if not self.instance.check_password(old_password):
raise forms.ValidationError('Incorrect old password')
return old_password
def save(self, commit=True):
user = self.instance
user.set_password(self.cleaned_data.get('password'))
if commit:
user.save()
return user
class Meta:
model = get_user_model()
fields = ['old_password', 'password', 'password_confirm']
# Generated by Django 3.2.19 on 2023-07-31 10:12
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Profile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('birth_date', models.DateTimeField(blank=True, null=True, verbose_name='Дата рождения')),
('avatar', models.ImageField(blank=True, null=True, upload_to='avatars', verbose_name='Аватар')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL, verbose_name='Пользователь')),
],
options={
'verbose_name': 'Профиль',
'verbose_name_plural': 'Профили',
},
),
]
# Generated by Django 3.2.19 on 2023-07-31 10:45
from django.db import migrations
def create_profiles(apps, schema_editor):
User = apps.get_model('accounts', 'User')
Profile = apps.get_model('accounts', 'Profile')
for user in User.objects.filter(profile=None):
Profile.objects.create(user=user)
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_profile'),
]
operations = [
migrations.RunPython(create_profiles, migrations.RunPython.noop)
]
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.contrib.auth import get_user_model
class User(AbstractUser):
pass
class Profile(models.Model):
user = models.OneToOneField(
get_user_model(),
related_name='profile',
on_delete=models.CASCADE,
verbose_name='Пользователь'
)
birth_date = models.DateTimeField(null=True, blank=True, verbose_name='Дата рождения')
avatar = models.ImageField(null=True, blank=True, upload_to='avatars', verbose_name='Аватар')
def __str__(self):
return self.user.username + "'s Profile"
class Meta:
verbose_name = 'Профиль'
verbose_name_plural = 'Профили'
{% extends 'base.html' %}
{% block title %}
<h1>Смена пароля</h1>
{% endblock %}
{% block content %}
{% url "accounts:password_change" user_obj.pk as action_url %}
<form action="{{ action_url }}" method="post">
{% csrf_token %}
{% include 'partial/form.html' with button_text='Change' %}
</form>
{% endblock %}
\ No newline at end of file
{% extends 'base.html' %}
{% block title %}
<h1 class="my-4">Cabinet</h1>
{% endblock %}
{% block content %}
<h1>Личная страница пользователя {{ user.first_name }} {{ user.last_name }}</h1>
{% if user.profile.avatar %}
<img src="{{ user.profile.avatar.url }}" width="250" height="250" alt="user avatar">
{% endif %}
<p>Имя пользователя: {{ user.username }}</p>
<p>Дата рождения: {{ user.profile.birth_date|date:'d.m.Y' }}</p>
<p>Почта: {{ user.email }}</p>
<a href="{% url 'user_update' user.id %}" class="btn btn-primary mb-3">Update</a>
<a href="{% url 'user_change_pass' user.id %}" class="btn btn-primary mb-3">Change Password</a>
<h2>Статьи автора</h2>
{% include 'partial/article_list.html' %}
{% endblock %}
{% extends 'base.html' %}
{% block content %}
<h1>Поменять личные данные</h1>
<form action="{% url 'user_update' user_obj.id %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{% include 'partial/form.html' with form=form %}
{% include 'partial/form.html' with form=profile_form %}
<br>
<input type="submit" value="Submit" class="btn btn-success">
</form>
{% endblock %}
\ No newline at end of file
......@@ -5,5 +5,8 @@ from accounts import views
urlpatterns = [
path('login/', views.LoginView.as_view(), name='login'),
path('logout/', views.LogoutView.as_view(), name='logout'),
path('register/', views.RegisterView.as_view(), name='register')
path('register/', views.RegisterView.as_view(), name='register'),
path('users/<int:id>/detail/', views.UserDetailView.as_view(), name='user_detail'),
path('users/<int:id>/update/', views.UserUpdateView.as_view(), name='user_update'),
path('users/<int:id>/change-pass/', views.UserPasswordChangeView.as_view(), name='user_change_pass')
]
from django.contrib.auth import views, login
from django.views.generic import CreateView
from django.contrib.auth import views, login, get_user_model, update_session_auth_hash
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.auth.views import PasswordChangeView
from django.core.paginator import Paginator
from django.http import HttpResponseRedirect
from django.views.generic import CreateView, DetailView, UpdateView
from django.shortcuts import redirect
from django.urls import reverse_lazy
from accounts.forms import LoginForm, RegisterForm
from accounts.models import User
from accounts.forms import LoginForm, RegisterForm, UserUpdateForm, ProfileUpdateForm, PasswordChangeForm
from accounts.models import User, Profile
class AuthSuccessUrlMixin:
......@@ -35,5 +39,90 @@ class RegisterView(AuthSuccessUrlMixin, CreateView):
def form_valid(self, form):
user = form.save()
Profile.objects.create(user=user)
login(self.request, user)
return redirect(self.get_success_url())
class UserDetailView(LoginRequiredMixin, DetailView):
model = get_user_model() # accounts.User
template_name = 'user/detail.html'
context_object_name = 'user'
pk_url_kwarg = 'id'
paginate_related_by = 5
paginate_related_orphans = 0
def get_context_data(self, **kwargs):
articles = self.object.articles.order_by('-created_at')
paginator = Paginator(
object_list=articles,
per_page=self.paginate_related_by,
orphans=self.paginate_related_orphans
)
page_number = self.request.GET.get('page', 1)
page = paginator.get_page(page_number)
kwargs |= {
'page_obj': page,
'articles': page.object_list,
'is_paginated': page.has_other_pages(),
'paginator': paginator
}
return super().get_context_data(**kwargs)
class UserUpdateView(UserPassesTestMixin, UpdateView):
model = get_user_model()
form_class = UserUpdateForm
template_name = 'user/update.html'
pk_url_kwarg = 'id'
context_object_name = 'user_obj'
def test_func(self):
return self.request.user == self.get_object()
def get_context_data(self, **kwargs):
if 'profile_form' not in kwargs:
kwargs['profile_form'] = self.get_profile_form()
return super().get_context_data(**kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
profile_form = self.get_profile_form()
if form.is_valid() and profile_form.is_valid():
profile_form.save()
return self.form_valid(form)
else:
context = self.get_context_data(form=form, profile_form=profile_form)
return self.render_to_response(context)
def get_success_url(self):
return reverse_lazy('user_detail', kwargs={'id': self.object.id})
def get_profile_form(self):
form_kwargs = {'instance': self.object.profile}
if self.request.method == 'POST':
form_kwargs['data'] = self.request.POST
form_kwargs['files'] = self.request.FILES
return ProfileUpdateForm(**form_kwargs)
class UserPasswordChangeView(UserPassesTestMixin, UpdateView):
model = get_user_model()
template_name = 'user/change_password.html'
form_class = PasswordChangeForm
pk_url_kwarg = 'id'
context_object_name = 'user_obj'
def test_func(self):
return self.request.user == self.get_object()
def form_valid(self, form):
user = form.save()
update_session_auth_hash(self.request, user)
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return reverse_lazy('user_detail', kwargs={'id': self.object.id})
......@@ -129,9 +129,9 @@ STATICFILES_DIRS = [
#
#
# # Media files
#
# MEDIA_URL = '/media/'
# MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
......
......@@ -239,7 +239,7 @@
"fields": {
"name": "Can delete article",
"content_type": 7,
"codename": "delete_article"
"codename": "article_delete"
}
},
{
......
File added
asgiref==3.7.2
blessed==1.20.0
bpython==0.24
certifi==2023.5.7
charset-normalizer==3.2.0
curtsies==0.4.1
cwcwidth==0.1.8
Django==3.2.19
django-extensions==3.2.3
greenlet==2.0.2
idna==3.4
Pillow==10.0.0
Pygments==2.15.1
pytz==2023.3
pyxdg==0.28
requests==2.31.0
six==1.16.0
sqlparse==0.4.4
urllib3==2.0.3
wcwidth==0.2.6
......@@ -28,7 +28,7 @@
{% if perms.web.change_article or article.author == request.user %}
<a href="{% url 'article_update' article.pk %}" class="btn btn-primary mt-4 me-3">Edit</a>
{% endif %}
<form action="{% url 'delete_article' article.pk %}" method="POST" onsubmit="return confirm('Are you sure?')">
<form action="{% url 'article_delete' article.pk %}" method="POST" onsubmit="return confirm('Are you sure?')">
{% csrf_token %}
<button class="btn btn-danger mt-4">Delete</button>
</form>
......
......@@ -6,11 +6,11 @@
{% block content %}
<h1>Are you sure you want to delete article: {{ article.title }}?</h1>
<form action="{% url 'delete_article' article.pk %}" method="POST">
<form action="{% url 'article_delete' article.pk %}" method="POST">
{% csrf_token %}
<p><input type="submit" value="Confirm"></p>
<p><a href="{% url 'articles-detail' article.pk %}">Cancel</a></p>
<p><a href="{% url 'article_detail' article.pk %}">Cancel</a></p>
</form>
......
......@@ -5,38 +5,6 @@
{% block content %}
<h1>{{ "Articles"|upper }}</h1>
<h1>{{ greetings }}</h1>
{% custom_range 1 10 as range %}
{% for num in range %}
{{ num }}
{% endfor %}
{% if perms.web.add_article %}
<a href="{% url 'articles-add' %}" class="btn btn-primary">Создать</a>
{% endif %}
{% include 'partial/search_form.html' %}
{% for article in articles %}
<br>
<hr>
<br>
<h2>{{ article.title }}</h2>
{# {% if user.is_authenticated %}#}
<p>
<a href="{% url 'articles-detail' id=article.id %}">
Подробнее
</a>
</p>
{# {% endif %}#}
<br>
<hr>
<br>
{% endfor %}
{% if is_paginated %}
{% include 'partial/pagination.html' %}
{% endif %}
{% include 'partial/article_list.html' %}
{% endblock %}
......@@ -23,7 +23,7 @@
<div class="collapse navbar-collapse flex-grow-0" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="{% url 'articles-add' %}">
<a class="nav-link active" aria-current="page" href="{% url 'article_add' %}">
Create
</a>
</li>
......@@ -31,14 +31,14 @@
<a class="nav-link" href="#">Features</a>
</li>
{% if user.is_authenticated %}
{% if request.user.is_authenticated %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
aria-expanded="false">
{{ user.username }}
{{ request.user.username }}
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="{% url 'user_detail' request.user.id %}">Cabinet</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li>
<hr class="dropdown-divider">
......
{% if is_paginated %}
{% include 'partial/pagination.html' %}
{% endif %}
{% for article in articles %}
<br>
<hr>
<br>
<h2>{{ article.title }}</h2>
<p>
<a href="{% url 'article_detail' id=article.id %}">
Подробнее
</a>
</p>
<br>
<hr>
<br>
{% endfor %}
{% if is_paginated %}
{% include 'partial/pagination.html' %}
{% endif %}
\ No newline at end of file
......@@ -17,5 +17,6 @@
{{ field }}
{% endfor %}
<button type="submit" class="btn btn-success">{{ button_text }}</button>
{% if button_text %}
<button type="submit" class="btn btn-success">{{ button_text }}</button>
{% endif %}
......@@ -4,10 +4,10 @@ from web import views
urlpatterns = [
path('articles/', views.ArticleIndexView.as_view(), name='main_page'),
path('articles/add/', views.ArticleCreateView.as_view(), name='articles-add'),
path('articles/<int:id>/', views.ArticleDetailView.as_view(), name='articles-detail'),
path('articles/add/', views.ArticleCreateView.as_view(), name='article_add'),
path('articles/<int:id>/', views.ArticleDetailView.as_view(), name='article_detail'),
path('articles/<int:id>/edit', views.ArticleUpdateView.as_view(), name='article_update'),
path('articles/<int:id>/delete', views.ArticleDeleteView.as_view(), name='delete_article'),
path('articles/<int:id>/delete', views.ArticleDeleteView.as_view(), name='article_delete'),
path(
'comments/add/',
......
......@@ -52,7 +52,7 @@ class ArticleIndexView(ListView):
return self.form.cleaned_data.get('search')
class ArticleCreateView(PermissionRequiredMixin, CreateView):
class ArticleCreateView(CreateView):
template_name = 'article/article_create.html'
model = Article
form_class = ArticleModelForm
......@@ -60,7 +60,7 @@ class ArticleCreateView(PermissionRequiredMixin, CreateView):
permission_denied_message = 'You have no rights'
def get_success_url(self):
return reverse('articles-detail', kwargs={'id': self.object.id})
return reverse('article_detail', kwargs={'id': self.object.id})
class ArticleDetailView(DetailView):
......@@ -91,7 +91,7 @@ class ArticleUpdateView(UserPassesTestMixin, UpdateView):
self.request.user.has_perm('web.change_article')
def get_success_url(self):
return reverse('articles-detail', kwargs={'id': self.object.id})
return reverse('article_detail', kwargs={'id': self.object.id})
class ArticleDeleteView(DeleteView):
......
......@@ -15,7 +15,7 @@ class CommentCreateView(CreateView):
form_class = CommentModelForm
def get_success_url(self):
return reverse('articles-detail', kwargs={'id': self.object.article.id})
return reverse('article_detail', kwargs={'id': self.object.article.id})
class CommentUpdateView(PermissionRequiredMixin, UpdateView):
......@@ -34,7 +34,7 @@ class CommentUpdateView(PermissionRequiredMixin, UpdateView):
return super().has_permission() or self.request.user == self.get_object().author
def get_success_url(self):
return reverse('articles-detail', kwargs={'id': self.object.article.id})
return reverse('article_detail', kwargs={'id': self.object.article.id})
class CommentDeleteView(DeleteView):
......@@ -42,4 +42,4 @@ class CommentDeleteView(DeleteView):
pk_url_kwarg = 'id'
def get_success_url(self):
return reverse('articles-detail', kwargs={'id': self.object.article.id})
return reverse('article_detail', kwargs={'id': self.object.article.id})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment