/
nmktth
/
web-development-sem-4
Обзор
Документация
Войти
/
nmktth
/
web-development-sem-4
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
realty/views.py
759 строк
26 KB
artem
feat: complete Realty frontend, accessibility and lab reports
02 июн 2026, 08:46
02 июн 2026, 08:46
873b76d
Код
Авторство
О чём код?
import json from urllib.parse import urlparse from django.contrib.auth import authenticate, login from django.contrib.auth.decorators import login_required from django.contrib.auth.forms import UserCreationForm from django.core.exceptions import PermissionDenied from django.db.models import Avg, Max, Count, Q, F from django import forms from django.http import JsonResponse from django.shortcuts import get_object_or_404, redirect, render from .models import ( Advertisement, User, Favorite, Chat, Message, NeighborRequest, Complaint, News ) # --- FORMS --- class AdForm(forms.ModelForm): class Meta: model = Advertisement fields = [ 'title', 'description', 'price', 'deposit', 'city', 'address', 'metro', 'type', 'deal_type', 'image', 'contract_template', 'video_url'] def clean_video_url(self): url = self.cleaned_data.get('video_url') if url: parsed = urlparse(url) allowed_hosts = { 'youtube.com', 'www.youtube.com', 'm.youtube.com', 'youtu.be', } if ( parsed.scheme not in {'http', 'https'} or parsed.hostname not in allowed_hosts ): raise forms.ValidationError( "Поддерживаются только ссылки на YouTube") return url class NeighborRequestForm(forms.ModelForm): class Meta: model = NeighborRequest fields = ['city', 'description', 'looking_for_apartment'] class ComplaintForm(forms.ModelForm): class Meta: model = Complaint fields = ['reason'] class SignUpForm(UserCreationForm): class Meta(UserCreationForm.Meta): model = User fields = ('username', 'email', 'first_name', 'last_name') class UserProfileForm(forms.ModelForm): class Meta: model = User fields = [ 'first_name', 'last_name', 'middle_name', 'email', 'about', 'avatar'] # --- API HELPER --- def get_ad_dict(ad, favorite_ids=[]): return { 'id': ad.id, 'title': ad.title, 'description': ad.description, 'city': ad.city, 'address': ad.address, 'metro': ad.metro, 'price': str(ad.price), 'deposit': str(ad.deposit) if ad.deposit else None, 'type': ad.get_type_display(), 'deal_type': ad.get_deal_type_display(), 'image_url': ad.image.url if ad.image else None, 'contract_template': ( ad.contract_template.url if ad.contract_template else None), 'video_url': ad.video_url, 'created_at': ad.created_at.strftime('%d.%m.%Y'), 'views_count': ad.views_count, 'is_favorite': ad.id in favorite_ids, 'author': { 'id': ad.user.id, 'name': f"{ad.user.first_name} {ad.user.last_name}", 'avatar': ad.user.avatar.url if ad.user.avatar else None, 'joined': ad.user.date_joined.strftime('%b %Y') } if getattr(ad, 'user', None) else None } def get_user_dict(user): if not user.is_authenticated: return { 'id': None, 'is_authenticated': False, 'username': '', 'first_name': '', 'last_name': '', 'email': '', 'avatar': None, 'role': None, } return { 'id': user.id, 'is_authenticated': True, 'username': user.username, 'first_name': user.first_name, 'last_name': user.last_name, 'email': user.email, 'avatar': user.avatar.url if user.avatar else None, 'role': getattr(user, 'role', 'user'), } # --- ОБЪЯВЛЕНИЯ --- def ads_list(request): search_query = request.GET.get('q', '') min_price = request.GET.get('min_price') max_price = request.GET.get('max_price') ad_type = request.GET.get('type') sort_by = request.GET.get('sort') page = int(request.GET.get('page', 1)) limit = 30 offset = (page - 1) * limit queryset = Advertisement.active_objects.select_related('user').all() if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(city__icontains=search_query) | Q(address__icontains=search_query) | Q(metro__icontains=search_query)) if min_price and min_price.isdigit(): queryset = queryset.filter(price__gte=min_price) if max_price and max_price.isdigit(): queryset = queryset.filter(price__lte=max_price) if ad_type: queryset = queryset.filter(type=ad_type) total_count = queryset.count() if sort_by == 'popular': sorted_ads = queryset.order_by('-views_count')[offset:offset + limit] else: sorted_ads = queryset.order_by('-created_at')[offset:offset + limit] has_more = total_count > offset + limit top_ads = Advertisement.active_objects.select_related( 'user').order_by('-views_count')[:4] news = News.objects.all().order_by('-created_at')[:10] city_stats = list(Advertisement.objects.values( 'city').annotate( count=Count('id'), avg_p=Avg('price') ).order_by('-count')[:5]) neighbors = NeighborRequest.objects.filter( is_active=True).select_related('user').order_by('-created_at')[:4] favorite_ids = [] if request.user.is_authenticated: favorite_ids = list( Favorite.objects.filter( user=request.user).values_list( 'advertisement_id', flat=True)) if request.GET.get('format') == 'json': data = { 'ads': [ get_ad_dict( ad, favorite_ids) for ad in sorted_ads], 'has_more': has_more, 'top_ads': [ get_ad_dict( ad, favorite_ids) for ad in top_ads] if page == 1 else [], 'city_stats': city_stats if page == 1 else [], 'news': [ { 'id': n.id, 'title': n.title, 'content': n.content, 'image': n.image.url if n.image else None, 'created_at': n.created_at.strftime('%d.%m.%Y') } for n in news] if page == 1 else [], 'neighbors': [ { 'id': n.id, 'user_id': n.user.id, 'user_name': n.user.first_name, 'avatar': ( n.user.avatar.url if n.user.avatar else None), 'city': n.city, 'desc': n.description } for n in neighbors] if page == 1 else [], 'total_found': total_count, # ИСПРАВЛЕНО: Считаем только активные! 'total_ads': Advertisement.active_objects.count(), 'user': get_user_dict(request.user), } return JsonResponse(data) return render(request, 'realty/base.html') def user_profile(request, pk): target_user = get_object_or_404(User, pk=pk) if request.GET.get('format') == 'json': page = int(request.GET.get('page', 1)) limit = 30 offset = (page - 1) * limit ads_qs = Advertisement.active_objects.filter( user=target_user).order_by('-created_at') total_ads = ads_qs.count() ads = ads_qs[offset:offset + limit] has_more_ads = total_ads > offset + limit reqs = NeighborRequest.objects.filter( user=target_user, is_active=True).order_by('-created_at') favorite_ids = [] if request.user.is_authenticated: favorite_ids = list( Favorite.objects.filter( user=request.user).values_list( 'advertisement_id', flat=True)) return JsonResponse({ 'profile': { 'id': target_user.id, 'first_name': target_user.first_name, 'last_name': target_user.last_name, 'middle_name': target_user.middle_name, 'email': target_user.email, 'about': target_user.about, 'avatar': ( target_user.avatar.url if target_user.avatar else None), 'date_joined': target_user.date_joined.strftime('%b %Y'), 'role': getattr(target_user, 'role', 'user') }, 'is_owner': request.user == target_user, 'ads': [get_ad_dict(ad, favorite_ids) for ad in ads], 'has_more_ads': has_more_ads, 'neighbors': [{ 'id': n.id, 'city': n.city, 'description': n.description, 'avatar': ( target_user.avatar.url if target_user.avatar else None), 'created_at': n.created_at.strftime('%d.%m.%Y') } for n in reqs] }) return render(request, 'realty/base.html') def ad_detail(request, pk): ad = get_object_or_404(Advertisement.objects.select_related('user'), pk=pk) Advertisement.objects.filter( pk=pk).update( views_count=F('views_count') + 1) if request.GET.get('format') == 'json': favorite_ids = [] if request.user.is_authenticated: favorite_ids = list( Favorite.objects.filter( user=request.user, advertisement=ad).values_list( 'advertisement_id', flat=True)) data = get_ad_dict(ad, favorite_ids) data['is_owner'] = ( request.user == ad.user if request.user.is_authenticated else False) data['is_admin'] = ( request.user.is_superuser or getattr( request.user, 'role', '') == 'admin') if request.user.is_authenticated else False return JsonResponse(data) return render(request, 'realty/base.html') @login_required def ad_create(request): if request.method == 'POST': form = AdForm(request.POST, request.FILES) if form.is_valid(): ad = form.save(commit=False) ad.user = request.user ad.save() if request.GET.get('format') == 'json': return JsonResponse({'status': 'ok', 'id': ad.pk}) return redirect(ad.get_absolute_url()) else: if request.GET.get('format') == 'json': return JsonResponse( {'status': 'error', 'errors': form.errors}, status=400) else: form = AdForm() return render(request, 'realty/base.html') @login_required def ad_update(request, pk): ad = get_object_or_404(Advertisement, pk=pk) if ad.user != request.user and not request.user.is_superuser and getattr( request.user, 'role', '') != 'admin': if request.GET.get('format') == 'json': return JsonResponse({'error': 'Доступ запрещен'}, status=403) raise PermissionDenied if request.method == "POST": form = AdForm(request.POST, request.FILES, instance=ad) if form.is_valid(): form.save() if request.GET.get('format') == 'json': return JsonResponse({'status': 'ok', 'id': ad.pk}) return redirect('ad_detail', pk=ad.pk) else: if request.GET.get('format') == 'json': return JsonResponse({'errors': form.errors}, status=400) else: if request.GET.get('format') == 'json': return JsonResponse({ 'title': ad.title, 'description': ad.description, 'price': str(ad.price), 'deposit': str(ad.deposit) if ad.deposit else '', 'city': ad.city, 'address': ad.address, 'metro': ad.metro or '', 'type': ad.type, 'deal_type': ad.deal_type, 'video_url': ad.video_url or '' }) form = AdForm(instance=ad) return render(request, 'realty/base.html') @login_required def ad_delete(request, pk): ad = get_object_or_404(Advertisement, pk=pk) if ad.user != request.user and not request.user.is_superuser and getattr( request.user, 'role', '') != 'admin': raise PermissionDenied if request.method == "POST" or request.GET.get('format') == 'json': ad.delete() if request.GET.get('format') == 'json': return JsonResponse({'status': 'success'}) return redirect('ads_list') return render(request, 'realty/base.html') @login_required def toggle_favorite(request, pk): ad = get_object_or_404(Advertisement, pk=pk) favorite, created = Favorite.objects.get_or_create( user=request.user, advertisement=ad) if not created: favorite.delete() if request.GET.get('format') == 'json': return JsonResponse({'is_favorite': created}) return redirect(request.META.get('HTTP_REFERER', 'ads_list')) @login_required def report_ad(request, pk): ad = get_object_or_404(Advertisement, pk=pk) if request.method == 'POST': try: data = json.loads(request.body) reason = data.get('reason') except BaseException: reason = request.POST.get('reason') if reason: Complaint.objects.create( complainant=request.user, content_type='Advertisement', content_id=ad.id, reason=reason ) if request.GET.get('format') == 'json': return JsonResponse({'status': 'ok'}) return redirect('ad_detail', pk=pk) if request.GET.get('format') == 'json': return JsonResponse({'error': 'Reason is required'}, status=400) form = ComplaintForm(request.POST) if form.is_valid(): complaint = form.save(commit=False) complaint.complainant = request.user complaint.content_type = 'Advertisement' complaint.content_id = ad.id complaint.save() if request.GET.get('format') == 'json': return JsonResponse({'status': 'ok'}) return redirect('ad_detail', pk=pk) else: form = ComplaintForm() return render(request, 'realty/base.html') @login_required def neighbor_request_create(request): if request.method == 'POST': form = NeighborRequestForm(request.POST) if form.is_valid(): nr = form.save(commit=False) nr.user = request.user nr.save() if request.GET.get('format') == 'json': return JsonResponse({'status': 'ok', 'id': nr.pk}) return redirect('neighbor_list') else: if request.GET.get('format') == 'json': return JsonResponse({'errors': form.errors}, status=400) else: form = NeighborRequestForm() return render(request, 'realty/base.html') @login_required def neighbor_request_delete(request, pk): nr = get_object_or_404(NeighborRequest, pk=pk) if nr.user != request.user and not request.user.is_superuser and getattr( request.user, 'role', '') != 'admin': raise PermissionDenied if request.method == "POST" or request.GET.get('format') == 'json': nr.delete() if request.GET.get('format') == 'json': return JsonResponse({'status': 'success'}) return redirect('neighbor_list') return redirect('neighbor_list') def signup(request): if request.method == 'POST': form = SignUpForm(request.POST) if form.is_valid(): user = form.save() login(request, user) if request.GET.get('format') == 'json': return JsonResponse({'status': 'ok'}) return redirect('ads_list') else: if request.GET.get('format') == 'json': return JsonResponse( {'status': 'error', 'errors': form.errors}, status=400) else: form = SignUpForm() if request.GET.get('format') == 'json': return JsonResponse({'status': 'ready'}) return render(request, 'realty/base.html') def login_api(request): if request.method == 'POST': try: data = json.loads(request.body) username = data.get('username') password = data.get('password') except (json.JSONDecodeError, UnicodeDecodeError): username = request.POST.get('username') password = request.POST.get('password') user = authenticate(request, username=username, password=password) if user is not None: login(request, user) return JsonResponse({'status': 'ok'}) else: return JsonResponse( {'status': 'error', 'message': 'Неверный логин или пароль'}, status=400) return JsonResponse({'status': 'method_not_allowed'}, status=405) @login_required def start_chat(request, ad_pk): ad = get_object_or_404(Advertisement, pk=ad_pk) if ad.user == request.user: return redirect('ads_list') chat, _ = Chat.objects.get_or_create( user_1=min(request.user, ad.user, key=lambda u: u.id), user_2=max(request.user, ad.user, key=lambda u: u.id), advertisement=ad ) if request.GET.get('format') == 'json': return JsonResponse({'chat_id': chat.pk}) return redirect('chat_detail', pk=chat.pk) @login_required def start_chat_neighbor(request, user_id): target_user = get_object_or_404(User, pk=user_id) chat, _ = Chat.objects.get_or_create( advertisement__isnull=True, user_1=min(request.user, target_user, key=lambda u: u.id), user_2=max(request.user, target_user, key=lambda u: u.id) ) if request.GET.get('format') == 'json': return JsonResponse({'chat_id': chat.pk}) return redirect('chat_detail', pk=chat.pk) @login_required def chat_list(request): chats = Chat.objects.filter( Q(user_1=request.user) | Q(user_2=request.user) ).select_related( 'advertisement', 'user_1', 'user_2' ).order_by('-created_at') if request.GET.get('format') == 'json': chat_data = [] for c in chats: other_user = c.user_2 if c.user_1 == request.user else c.user_1 chat_data.append({ 'id': c.id, 'other_user': ( f"{other_user.first_name} {other_user.last_name}"), 'other_user_id': other_user.id, 'ad_title': ( c.advertisement.title if c.advertisement else 'Обсуждение соседства'), 'ad_id': c.advertisement.id if c.advertisement else None, }) return JsonResponse({'chats': chat_data}) return render(request, 'realty/base.html') @login_required def chat_detail(request, pk): chat = get_object_or_404(Chat, pk=pk) if request.user not in [chat.user_1, chat.user_2]: return JsonResponse({'error': 'Доступ запрещен'}, status=403) if request.method == 'POST': try: data = json.loads(request.body) text = data.get('text') except BaseException: text = request.POST.get('text') if text: Message.objects.create(chat=chat, sender=request.user, text=text) if request.GET.get('format') == 'json': return JsonResponse({'status': 'ok'}) return redirect('chat_detail', pk=pk) messages = chat.messages.select_related( 'sender').all().order_by('sent_at') if request.GET.get('format') == 'json': other_user = ( chat.user_2 if chat.user_1 == request.user else chat.user_1) return JsonResponse({ 'chat_id': chat.id, 'title': ( chat.advertisement.title if chat.advertisement else 'Обсуждение соседства'), 'ad_id': chat.advertisement.id if chat.advertisement else None, 'other_user': f"{other_user.first_name} {other_user.last_name}", 'other_user_id': other_user.id, 'messages': [{ 'id': m.id, 'text': m.text, 'is_me': m.sender == request.user, 'sent_at': m.sent_at.strftime('%H:%M') } for m in messages] }) return render(request, 'realty/base.html') @login_required def user_favorites(request): user_favs = Favorite.objects.select_related( 'advertisement', 'user').filter( user=request.user).order_by('-created_at') if request.GET.get('format') == 'json': favorite_ids = [f.advertisement.id for f in user_favs] return JsonResponse({'favorites': [get_ad_dict( f.advertisement, favorite_ids) for f in user_favs]}) return render(request, 'realty/base.html') @login_required def neighbor_requests_list(request): reqs = NeighborRequest.objects.filter(is_active=True).exclude( user=request.user).select_related('user').order_by('-created_at') if request.GET.get('format') == 'json': return JsonResponse({ 'neighbors': [{ 'id': n.id, 'city': n.city, 'description': n.description, 'author': f"{n.user.first_name} {n.user.last_name}", 'author_id': n.user.id, 'avatar': n.user.avatar.url if n.user.avatar else None, 'created_at': n.created_at.strftime('%d.%m.%Y') } for n in reqs] }) return render(request, 'realty/base.html') @login_required def neighbor_request_detail(request, pk): n = get_object_or_404( NeighborRequest.objects.select_related('user'), pk=pk) if request.GET.get('format') == 'json': return JsonResponse( { 'id': n.id, 'city': n.city, 'description': n.description, 'looking_for_apartment': n.looking_for_apartment, 'author': f"{ n.user.first_name} { n.user.last_name}", 'author_id': n.user.id, 'created_at': n.created_at.strftime('%d.%m.%Y'), 'is_owner': n.user == request.user, 'is_admin': ( request.user.is_superuser or getattr( request.user, 'role', '') == 'admin' ) if request.user.is_authenticated else False }) return render(request, 'realty/base.html') @login_required def admin_stats(request): if not ( request.user.is_superuser or getattr( request.user, 'role', None) == 'admin'): raise PermissionDenied total_ads_count = Advertisement.objects.count() city_prices = list( Advertisement.objects.values('city').annotate( avg_p=Avg('price'))) stats = Advertisement.objects.aggregate( avg_price=Avg('price'), max_views=Max('views_count')) if request.GET.get('format') == 'json': return JsonResponse({ 'total': total_ads_count, 'city_prices': city_prices, 'avg_price': stats['avg_price'], 'max_views': stats['max_views'] }) return render(request, 'realty/base.html') def news_detail(request, pk): n = get_object_or_404(News, pk=pk) if request.GET.get('format') == 'json': return JsonResponse({ 'id': n.id, 'title': n.title, 'content': n.content, 'image': n.image.url if n.image else None, 'created_at': n.created_at.strftime('%d.%m.%Y') }) return render(request, 'realty/base.html') def news_list(request): news = News.objects.all().order_by('-created_at') if request.GET.get('format') == 'json': return JsonResponse({ 'news': [ { 'id': n.id, 'title': n.title, 'content': ( n.content[:150] + '...' if len(n.content) > 150 else n.content), 'image': n.image.url if n.image else None, 'created_at': n.created_at.strftime('%d.%m.%Y') } for n in news] }) return render(request, 'realty/base.html') @login_required def profile_edit(request): if request.method == 'POST': form = UserProfileForm( request.POST, request.FILES, instance=request.user) if form.is_valid(): form.save() if request.GET.get('format') == 'json': return JsonResponse({'status': 'ok'}) return redirect('user_profile', pk=request.user.pk) else: if request.GET.get('format') == 'json': return JsonResponse({'errors': form.errors}, status=400) else: if request.GET.get('format') == 'json': return JsonResponse({ 'first_name': request.user.first_name, 'last_name': request.user.last_name, 'middle_name': request.user.middle_name, 'email': request.user.email, 'about': request.user.about, 'avatar': ( request.user.avatar.url if request.user.avatar else None) }) form = UserProfileForm(instance=request.user) return render(request, 'realty/base.html')