import logging from django.http import HttpResponseRedirect, JsonResponse from django.shortcuts import render, redirect from django.urls import reverse from django.contrib.auth.decorators import login_required from django.utils import translation from django.core.exceptions import PermissionDenied from django.contrib.auth.decorators import user_passes_test from .mail import mail_admins_new_report from notfellchen import settings from fellchensammlung import logger from .models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \ User, Location, AdoptionNoticeStatus, Subscriptions, CommentNotification, BaseNotification, RescueOrganization, \ Species, Log from .forms import AdoptionNoticeForm, AdoptionNoticeFormWithDateWidget, ImageForm, ReportAdoptionNoticeForm, \ CommentForm, ReportCommentForm, AnimalForm, \ AdoptionNoticeSearchForm, AnimalFormWithDateWidget, AdoptionNoticeFormWithDateWidgetAutoAnimal from .models import Language, Announcement from .tools.geo import GeoAPI from .tools.metrics import gather_metrics_data from .tools.admin import clean_locations, get_unchecked_adoption_notices, deactivate_unchecked_adoption_notices from .tasks import add_adoption_notice_location def user_is_trust_level_or_above(user, trust_level=User.MODERATOR): return user.is_authenticated and user.trust_level >= User.TRUST_LEVEL[trust_level] def user_is_owner_or_trust_level(user, django_object, trust_level=User.MODERATOR): return user.is_authenticated and ( user.trust_level == User.TRUST_LEVEL[trust_level] or django_object.owner == user) def fail_if_user_not_owner_or_trust_level(user, django_object, trust_level=User.MODERATOR): if not user_is_owner_or_trust_level(user, django_object, trust_level): raise PermissionDenied def index(request): """View function for home page of site.""" latest_adoption_list = AdoptionNotice.objects.filter( adoptionnoticestatus__major_status=AdoptionNoticeStatus.ACTIVE).order_by("-created_at") active_adoptions = [adoption for adoption in latest_adoption_list if adoption.is_active] language_code = translation.get_language() lang = Language.objects.get(languagecode=language_code) active_announcements = Announcement.get_active_announcements(lang) context = {"adoption_notices": active_adoptions[:5], "adoption_notices_map": active_adoptions, "announcements": active_announcements} Text.get_texts(["how_to", "introduction"], lang, context) return render(request, 'fellchensammlung/index.html', context=context) def change_language(request): if request.method == 'POST': language_code = request.POST.get('language') if language_code: if language_code != settings.LANGUAGE_CODE and language_code in list(zip(*settings.LANGUAGES))[0]: redirect_path = f'/{language_code}/' elif language_code == settings.LANGUAGE_CODE: redirect_path = '/' else: response = HttpResponseRedirect('/') return response translation.activate(language_code) response = HttpResponseRedirect(redirect_path) response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language_code) return response def adoption_notice_detail(request, adoption_notice_id): adoption_notice = AdoptionNotice.objects.get(id=adoption_notice_id) if request.user.is_authenticated: try: subscription = Subscriptions.objects.get(owner=request.user, adoption_notice=adoption_notice) is_subscribed = True except Subscriptions.DoesNotExist: is_subscribed = False else: is_subscribed = False has_edit_permission = user_is_owner_or_trust_level(request.user, adoption_notice) if request.method == 'POST': action = request.POST.get("action") if request.user.is_authenticated: if action == "comment": comment_form = CommentForm(request.POST) if comment_form.is_valid(): comment_instance = comment_form.save(commit=False) comment_instance.adoption_notice_id = adoption_notice_id comment_instance.user = request.user comment_instance.save() """Log""" Log.objects.create(user=request.user, action="comment", text=f"{request.user} hat Kommentar {comment_instance.pk} zur Vermittlung {adoption_notice_id} hinzugefügt") # Auto-subscribe user to adoption notice subscription, created = Subscriptions.objects.get_or_create(adoption_notice=adoption_notice, owner=request.user) subscription.save() # Notify users that a comment was added for subscription in adoption_notice.get_subscriptions(): # Create a notification but only if the user is not the one that posted the comment if subscription.owner != request.user: notification = CommentNotification(user=subscription.owner, title=f"{adoption_notice.name} - Neuer Kommentar", text=f"{request.user}: {comment_instance.text}", comment=comment_instance) notification.save() else: comment_form = CommentForm(instance=adoption_notice) if action == "subscribe": Subscriptions.objects.create(owner=request.user, adoption_notice=adoption_notice) is_subscribed = True if action == "unsubscribe": subscription.delete() is_subscribed = False else: raise PermissionDenied else: comment_form = CommentForm(instance=adoption_notice) context = {"adoption_notice": adoption_notice, "comment_form": comment_form, "user": request.user, "has_edit_permission": has_edit_permission, "is_subscribed": is_subscribed} return render(request, 'fellchensammlung/details/detail_adoption_notice.html', context=context) @login_required() def adoption_notice_edit(request, adoption_notice_id): """ Form to update adoption notices """ adoption_notice = AdoptionNotice.objects.get(pk=adoption_notice_id) fail_if_user_not_owner_or_trust_level(request.user, adoption_notice) if request.method == 'POST': form = AdoptionNoticeForm(request.POST, instance=adoption_notice) if form.is_valid(): adoption_notice_instance = form.save() """Search the location given in the location string and add it to the adoption notice""" location = Location.get_location_from_string(adoption_notice_instance.location_string) adoption_notice_instance.location = location adoption_notice_instance.save() """Log""" Log.objects.create(user=request.user, action="adoption_notice_edit", text=f"{request.user} hat Vermittlung {adoption_notice.pk} geändert") return redirect(reverse("adoption-notice-detail", args=[adoption_notice_instance.pk], )) else: form = AdoptionNoticeForm(instance=adoption_notice) return render(request, 'fellchensammlung/forms/form-adoption-notice.html', context={"form": form}) def animal_detail(request, animal_id): animal = Animal.objects.get(id=animal_id) context = {"animal": animal} return render(request, 'fellchensammlung/details/detail_animal.html', context=context) def search(request): place_not_found = None if request.method == 'POST': latest_adoption_list = AdoptionNotice.objects.order_by("-created_at") active_adoptions = [adoption for adoption in latest_adoption_list if adoption.is_active] search_form = AdoptionNoticeSearchForm(request.POST) max_distance = int(request.POST.get('max_distance')) if max_distance == "": max_distance = None geo_api = GeoAPI() search_position = geo_api.get_coordinates_from_query(request.POST['postcode']) if search_position is None: place_not_found = True adoption_notices_in_distance = active_adoptions else: adoption_notices_in_distance = [a for a in active_adoptions if a.in_distance(search_position, max_distance)] context = {"adoption_notices": adoption_notices_in_distance, "search_form": search_form, "place_not_found": place_not_found} else: latest_adoption_list = AdoptionNotice.objects.order_by("-created_at") active_adoptions = [adoption for adoption in latest_adoption_list if adoption.is_active] search_form = AdoptionNoticeSearchForm() context = {"adoption_notices": active_adoptions, "search_form": search_form} return render(request, 'fellchensammlung/search.html', context=context) @login_required def add_adoption_notice(request): if request.method == 'POST': form = AdoptionNoticeFormWithDateWidgetAutoAnimal(request.POST, request.FILES, in_adoption_notice_creation_flow=True) if form.is_valid(): instance = form.save(commit=False) instance.owner = request.user instance.save() """Spin up a task that adds the location""" add_adoption_notice_location.delay_on_commit(instance.pk) # Set correct status if request.user.trust_level >= User.TRUST_LEVEL[User.COORDINATOR]: major_status = AdoptionNoticeStatus.ACTIVE minor_status = AdoptionNoticeStatus.MINOR_STATUS_CHOICES[AdoptionNoticeStatus.ACTIVE]["searching"] else: major_status = AdoptionNoticeStatus.AWAITING_ACTION minor_status = AdoptionNoticeStatus.MINOR_STATUS_CHOICES[AdoptionNoticeStatus.AWAITING_ACTION][ "waiting_for_review"] status = AdoptionNoticeStatus.objects.create(major_status=major_status, minor_status=minor_status, adoption_notice=instance) status.save() # Get the species and number of animals from the form species = form.cleaned_data["species"] sex = form.cleaned_data["sex"] num_animals = form.cleaned_data["num_animals"] date_of_birth = form.cleaned_data["date_of_birth"] for i in range(0, num_animals): Animal.objects.create(owner=request.user, name=f"{species} {i + 1}", adoption_notice=instance, species=species, sex=sex, date_of_birth=date_of_birth) """Log""" Log.objects.create(user=request.user, action="add_adoption_notice", text=f"{request.user} hat Vermittlung {instance.pk} hinzugefügt") return redirect(reverse("adoption-notice-detail", args=[instance.pk])) else: form = AdoptionNoticeFormWithDateWidgetAutoAnimal(in_adoption_notice_creation_flow=True) return render(request, 'fellchensammlung/forms/form_add_adoption.html', {'form': form}) @login_required def adoption_notice_add_animal(request, adoption_notice_id): # Only users that are mods or owners of the adoption notice are allowed to add to it adoption_notice = AdoptionNotice.objects.get(pk=adoption_notice_id) fail_if_user_not_owner_or_trust_level(request.user, adoption_notice) if request.method == 'POST': form = AnimalFormWithDateWidget(request.POST, request.FILES) if form.is_valid(): instance = form.save(commit=False) instance.adoption_notice_id = adoption_notice_id instance.owner = request.user instance.save() form.save_m2m() if "save-and-add-another-animal" in request.POST: form = AnimalFormWithDateWidget(in_adoption_notice_creation_flow=True) return render(request, 'fellchensammlung/forms/form_add_animal_to_adoption.html', {'form': form}) else: return redirect(reverse("adoption-notice-detail", args=[adoption_notice_id])) else: form = AnimalFormWithDateWidget(in_adoption_notice_creation_flow=True) return render(request, 'fellchensammlung/forms/form_add_animal_to_adoption.html', {'form': form}) @login_required def add_photo_to_animal(request, animal_id): animal = Animal.objects.get(id=animal_id) # Only users that are mods or owners of the animal are allowed to add to it fail_if_user_not_owner_or_trust_level(request.user, animal) if request.method == 'POST': form = ImageForm(request.POST, request.FILES) if form.is_valid(): instance = form.save(commit=False) instance.owner = request.user instance.save() animal.photos.add(instance) """Log""" Log.objects.create(user=request.user, action="add_photo_to_animal", text=f"{request.user} hat Foto {instance.pk} zum Tier {animal.pk} hinzugefügt") if "save-and-add-another" in request.POST: form = ImageForm(in_flow=True) return render(request, 'fellchensammlung/forms/form-image.html', {'form': form}) else: return redirect(reverse("animal-detail", args=[animal_id])) else: form = ImageForm(in_flow=True) return render(request, 'fellchensammlung/forms/form-image.html', {'form': form}) @login_required def add_photo_to_adoption_notice(request, adoption_notice_id): adoption_notice = AdoptionNotice.objects.get(id=adoption_notice_id) # Only users that are mods or owners of the adoption notice are allowed to add to it fail_if_user_not_owner_or_trust_level(request.user, adoption_notice) if request.method == 'POST': form = ImageForm(request.POST, request.FILES) if form.is_valid(): instance = form.save(commit=False) instance.owner = request.user instance.save() adoption_notice.photos.add(instance) """Log""" Log.objects.create(user=request.user, action="add_photo_to_animal", text=f"{request.user} hat Foto {instance.pk} zur Vermittlung {adoption_notice.pk} hinzugefügt") if "save-and-add-another" in request.POST: form = ImageForm(in_flow=True) return render(request, 'fellchensammlung/forms/form-image.html', {'form': form}) else: return redirect(reverse("adoption-notice-detail", args=[adoption_notice_id])) else: return render(request, 'fellchensammlung/forms/form-image.html', {'form': form}) else: form = ImageForm(in_flow=True) return render(request, 'fellchensammlung/forms/form-image.html', {'form': form}) @login_required def animal_edit(request, animal_id): """ View implements the following methods * Updating an Animal """ animal = Animal.objects.get(pk=animal_id) # Only users that are mods or owners of the animal are allowed to edit it fail_if_user_not_owner_or_trust_level(request.user, animal) if request.method == 'POST': form = AnimalForm(request.POST, instance=animal) if form.is_valid(): animal = form.save() """Log""" Log.objects.create(user=request.user, action="add_photo_to_animal", text=f"{request.user} hat Tier {animal.pk} zum Tier geändert") return redirect(reverse("animal-detail", args=[animal.pk], )) else: form = AnimalForm(instance=animal) return render(request, 'fellchensammlung/forms/form-adoption-notice.html', context={"form": form}) def about(request): rules = Rule.objects.all() language_code = translation.get_language() lang = Language.objects.get(languagecode=language_code) legal = {} for text_code in ["terms_of_service", "privacy_statement", "imprint"]: try: legal[text_code] = Text.objects.get(text_code=text_code, language=lang, ) except Text.DoesNotExist: legal[text_code] = None context = {"rules": rules, } context.update(legal) return render( request, "fellchensammlung/about.html", context=context ) def report_adoption(request, adoption_notice_id): """ Form to report adoption notices """ if request.method == 'POST': form = ReportAdoptionNoticeForm(request.POST) if form.is_valid(): report_instance = form.save(commit=False) report_instance.adoption_notice_id = adoption_notice_id report_instance.status = Report.WAITING report_instance.save() form.save_m2m() mail_admins_new_report(report_instance) return redirect(reverse("report-detail-success", args=[report_instance.pk], )) else: form = ReportAdoptionNoticeForm() return render(request, 'fellchensammlung/forms/form-report.html', {'form': form}) def report_comment(request, comment_id): """ Form to report comments """ if request.method == 'POST': form = ReportCommentForm(request.POST) if form.is_valid(): report_instance = form.save(commit=False) report_instance.reported_comment_id = comment_id report_instance.status = Report.WAITING report_instance.save() form.save_m2m() mail_admins_new_report(report_instance) return redirect(reverse("report-detail-success", args=[report_instance.pk], )) else: form = ReportCommentForm() return render(request, 'fellchensammlung/forms/form-report.html', {'form': form}) def report_detail(request, report_id, form_complete=False): """ Detailed view of a report, including moderation actions """ report = Report.objects.get(pk=report_id) moderation_actions = ModerationAction.objects.filter(report_id=report_id) context = {"report": report, "moderation_actions": moderation_actions, "form_complete": form_complete} return render(request, 'fellchensammlung/details/detail-report.html', context) def report_detail_success(request, report_id): """ Calls the report detail view with form_complete set to true, so success message shows """ return report_detail(request, report_id, form_complete=True) @login_required def user_detail(request, user_id): user = User.objects.get(id=user_id) # Only users that are mods or owners of the user are allowed to view fail_if_user_not_owner_or_trust_level(request.user, user) if request.method == "POST": action = request.POST.get("action") if action == "notification_mark_read": notification_id = request.POST.get("notification_id") notification = CommentNotification.objects.get(pk=notification_id) notification.read = True notification.save() elif action == "notification_mark_all_read": notifications = CommentNotification.objects.filter(user=request.user, mark_read=False) for notification in notifications: notification.read = True notification.save() context = {"user": user, "adoption_notices": AdoptionNotice.objects.filter(owner=user), "notifications": CommentNotification.objects.filter(user=user, read=False)} return render(request, 'fellchensammlung/details/detail-user.html', context=context) @user_passes_test(user_is_trust_level_or_above) def modqueue(request): open_reports = Report.objects.filter(status=Report.WAITING) context = {"reports": open_reports} return render(request, 'fellchensammlung/modqueue.html', context=context) @login_required def updatequeue(request): #TODO: Make sure update can only be done for instances with permission if request.method == "POST": print(request.POST.get("adoption_notice_id")) adoption_notice = AdoptionNotice.objects.get(id=request.POST.get("adoption_notice_id")) action = request.POST.get("action") print(f"Action: {action}") if action == "checked_inactive": adoption_notice.set_closed() elif action == "checked_active": print("set checked") adoption_notice.set_checked() if user_is_trust_level_or_above(request.user, User.MODERATOR): last_checked_adoption_list = AdoptionNotice.objects.order_by("last_checked") else: last_checked_adoption_list = AdoptionNotice.objects.filter(owner=request.user).order_by("last_checked") adoption_notices = [adoption for adoption in last_checked_adoption_list if adoption.is_active] context = {"adoption_notices": adoption_notices} return render(request, 'fellchensammlung/updatequeue.html', context=context) def map(request): adoption_notices = AdoptionNotice.objects.all() #TODO: Filter to active context = {"adoption_notices_map": adoption_notices} return render(request, 'fellchensammlung/map.html', context=context) def metrics(request): data = gather_metrics_data() return JsonResponse(data) @login_required def instance_health_check(request): """ Allows an administrator to check common problems of an instance """ if request.method == "POST": action = request.POST.get("action") if action == "clean_locations": clean_locations(quiet=False) elif action == "deactivate_unchecked_adoption_notices": deactivate_unchecked_adoption_notices() number_of_adoption_notices = AdoptionNotice.objects.all().count() none_geocoded_adoption_notices = AdoptionNotice.objects.filter(location__isnull=True) number_not_geocoded_adoption_notices = len(none_geocoded_adoption_notices) number_of_rescue_orgs = RescueOrganization.objects.all().count() none_geocoded_rescue_orgs = RescueOrganization.objects.filter(location__isnull=True) number_not_geocoded_rescue_orgs = len(none_geocoded_rescue_orgs) unchecked_ans = get_unchecked_adoption_notices() number_unchecked_ans = len(unchecked_ans) # CHECK FOR MISSING TEXTS languages = Language.objects.all() texts = Text.objects.all() text_codes = set([text.text_code for text in texts]) missing_texts = [] for language in languages: for text_code in text_codes: try: Text.objects.get(text_code=text_code, language=language) except Text.DoesNotExist: missing_texts.append((text_code, language)) context = { "number_of_adoption_notices": number_of_adoption_notices, "number_not_geocoded_adoption_notices": number_not_geocoded_adoption_notices, "none_geocoded_adoption_notices": none_geocoded_adoption_notices, "number_of_rescue_orgs": number_of_rescue_orgs, "number_not_geocoded_rescue_orgs": number_not_geocoded_rescue_orgs, "none_geocoded_rescue_orgs": none_geocoded_rescue_orgs, "missing_texts": missing_texts, "number_unchecked_ans": number_unchecked_ans, "unchecked_ans": unchecked_ans } return render(request, 'fellchensammlung/instance-health-check.html', context=context) def external_site_warning(request): url = request.GET.get("url") context = {"url": url} language_code = translation.get_language() lang = Language.objects.get(languagecode=language_code) Text.get_texts(["external_site_warning", "good_adoption_practices"], language=lang) return render(request, 'fellchensammlung/external_site_warning.html', context=context)