kpsul/gestioncof/views.py
Ludovic Stephan 5fd4cb5c78 Change django.core.urlresolvers imports
-- compatible with 1.11 --
2019-03-19 10:18:56 +01:00

904 lines
32 KiB
Python

import uuid
from datetime import timedelta
import unicodecsv
from custommail.shortcuts import send_custom_mail
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.contrib.auth.views import (
login as django_login_view,
logout as django_logout_view,
redirect_to_login,
)
from django.contrib.sites.models import Site
from django.http import Http404, HttpResponse, HttpResponseForbidden
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView
from django_cas_ng.views import logout as cas_logout_view
from icalendar import Calendar, Event as Vevent
from bda.models import Spectacle, Tirage
from gestioncof.decorators import buro_required, cof_required
from gestioncof.forms import (
CalendarForm,
ClubsForm,
EventForm,
EventFormset,
EventStatusFilterForm,
GestioncofConfigForm,
ProfileForm,
RegistrationPassUserForm,
RegistrationProfileForm,
RegistrationUserForm,
SurveyForm,
SurveyStatusFilterForm,
UserForm,
)
from gestioncof.models import (
CalendarSubscription,
Club,
CofProfile,
Event,
EventCommentField,
EventCommentValue,
EventOption,
EventOptionChoice,
EventRegistration,
Survey,
SurveyAnswer,
SurveyQuestion,
SurveyQuestionAnswer,
)
from utils.views.autocomplete import Select2QuerySetView
@login_required
def home(request):
data = {
"surveys": Survey.objects.filter(old=False).all(),
"events": Event.objects.filter(old=False).all(),
"open_surveys": Survey.objects.filter(survey_open=True, old=False).all(),
"open_events": Event.objects.filter(registration_open=True, old=False).all(),
"active_tirages": Tirage.objects.filter(active=True).all(),
"open_tirages": Tirage.objects.filter(
active=True, ouverture__lte=timezone.now()
).all(),
"now": timezone.now(),
}
return render(request, "home.html", data)
def login(request):
if request.user.is_authenticated:
return redirect("home")
context = {}
if request.method == "GET" and "next" in request.GET:
context["next"] = request.GET["next"]
return render(request, "login_switch.html", context)
def login_ext(request):
if request.method == "POST" and "username" in request.POST:
try:
user = User.objects.get(username=request.POST["username"])
if not user.has_usable_password() or user.password in ("", "!"):
profile, created = CofProfile.objects.get_or_create(user=user)
if profile.login_clipper:
return render(
request, "error.html", {"error_type": "use_clipper_login"}
)
else:
return render(request, "error.html", {"error_type": "no_password"})
except User.DoesNotExist:
pass
context = {}
if request.method == "GET" and "next" in request.GET:
context["next"] = request.GET["next"]
if request.method == "POST" and "next" in request.POST:
context["next"] = request.POST["next"]
return django_login_view(request, template_name="login.html", extra_context=context)
@login_required
def logout(request, next_page=None):
if next_page is None:
next_page = request.GET.get("next", None)
profile = getattr(request.user, "profile", None)
if profile and profile.login_clipper:
msg = _("Déconnexion de GestioCOF et CAS réussie. À bientôt {}.")
logout_view = cas_logout_view
else:
msg = _("Déconnexion de GestioCOF réussie. À bientôt {}.")
logout_view = django_logout_view
messages.success(request, msg.format(request.user.get_short_name()))
return logout_view(request, next_page=next_page)
@login_required
def survey(request, survey_id):
survey = get_object_or_404(
Survey.objects.prefetch_related("questions", "questions__answers"), id=survey_id
)
if not survey.survey_open or survey.old:
raise Http404
success = False
deleted = False
if request.method == "POST":
form = SurveyForm(request.POST, survey=survey)
if request.POST.get("delete"):
try:
current_answer = SurveyAnswer.objects.get(
user=request.user, survey=survey
)
current_answer.delete()
current_answer = None
except SurveyAnswer.DoesNotExist:
current_answer = None
form = SurveyForm(survey=survey)
success = True
deleted = True
else:
if form.is_valid():
all_answers = []
for question_id, answers_ids in form.answers():
question = get_object_or_404(
SurveyQuestion, id=question_id, survey=survey
)
if type(answers_ids) != list:
answers_ids = [answers_ids]
if not question.multi_answers and len(answers_ids) > 1:
raise Http404
for answer_id in answers_ids:
if not answer_id:
continue
answer_id = int(answer_id)
answer = SurveyQuestionAnswer.objects.get(
id=answer_id, survey_question=question
)
all_answers.append(answer)
try:
current_answer = SurveyAnswer.objects.get(
user=request.user, survey=survey
)
except SurveyAnswer.DoesNotExist:
current_answer = SurveyAnswer(user=request.user, survey=survey)
current_answer.save()
current_answer.answers = all_answers
current_answer.save()
success = True
else:
try:
current_answer = SurveyAnswer.objects.get(user=request.user, survey=survey)
form = SurveyForm(survey=survey, current_answers=current_answer.answers)
except SurveyAnswer.DoesNotExist:
current_answer = None
form = SurveyForm(survey=survey)
# Messages
if success:
if deleted:
messages.success(request, "Votre réponse a bien été supprimée")
else:
messages.success(
request,
"Votre réponse a bien été enregistrée ! Vous "
"pouvez cependant la modifier jusqu'à la fin "
"du sondage.",
)
return render(
request,
"gestioncof/survey.html",
{"survey": survey, "form": form, "current_answer": current_answer},
)
def get_event_form_choices(event, form):
all_choices = []
for option_id, choices_ids in form.choices():
option = get_object_or_404(EventOption, id=option_id, event=event)
if type(choices_ids) != list:
choices_ids = [choices_ids]
if not option.multi_choices and len(choices_ids) > 1:
raise Http404
for choice_id in choices_ids:
if not choice_id:
continue
choice_id = int(choice_id)
choice = EventOptionChoice.objects.get(id=choice_id, event_option=option)
all_choices.append(choice)
return all_choices
def update_event_form_comments(event, form, registration):
for commentfield_id, value in form.comments():
field = get_object_or_404(EventCommentField, id=commentfield_id, event=event)
if value == field.default:
continue
(storage, _) = EventCommentValue.objects.get_or_create(
commentfield=field, registration=registration
)
storage.content = value
storage.save()
@login_required
def event(request, event_id):
event = get_object_or_404(Event, id=event_id)
if (not event.registration_open) or event.old:
raise Http404
success = False
if request.method == "POST":
form = EventForm(request.POST, event=event)
if form.is_valid():
all_choices = get_event_form_choices(event, form)
(current_registration, _) = EventRegistration.objects.get_or_create(
user=request.user, event=event
)
current_registration.options = all_choices
current_registration.save()
success = True
else:
try:
current_registration = EventRegistration.objects.get(
user=request.user, event=event
)
form = EventForm(event=event, current_choices=current_registration.options)
except EventRegistration.DoesNotExist:
form = EventForm(event=event)
# Messages
if success:
messages.success(
request,
"Votre inscription a bien été enregistrée ! "
"Vous pouvez cependant la modifier jusqu'à "
"la fin des inscriptions.",
)
return render(request, "gestioncof/event.html", {"event": event, "form": form})
def clean_post_for_status(initial):
d = initial.copy()
for k, v in d.items():
if k.startswith("id_"):
del d[k]
d[k[3:]] = v
return d
@buro_required
def event_status(request, event_id):
event = get_object_or_404(Event, id=event_id)
registrations_query = EventRegistration.objects.filter(event=event)
post_data = clean_post_for_status(request.POST)
form = EventStatusFilterForm(post_data or None, event=event)
if form.is_valid():
for option_id, choice_id, value in form.filters():
if option_id == "has_paid":
if value == "yes":
registrations_query = registrations_query.filter(paid=True)
elif value == "no":
registrations_query = registrations_query.filter(paid=False)
continue
choice = get_object_or_404(
EventOptionChoice, id=choice_id, event_option__id=option_id
)
if value == "none":
continue
if value == "yes":
registrations_query = registrations_query.filter(
options__id__exact=choice.id
)
elif value == "no":
registrations_query = registrations_query.exclude(
options__id__exact=choice.id
)
user_choices = registrations_query.prefetch_related("user").all()
options = EventOption.objects.filter(event=event).all()
choices_count = {}
for option in options:
for choice in option.choices.all():
choices_count[choice.id] = 0
for user_choice in user_choices:
for choice in user_choice.options.all():
choices_count[choice.id] += 1
return render(
request,
"event_status.html",
{
"event": event,
"user_choices": user_choices,
"options": options,
"choices_count": choices_count,
"form": form,
},
)
@buro_required
def survey_status(request, survey_id):
survey = get_object_or_404(Survey, id=survey_id)
answers_query = SurveyAnswer.objects.filter(survey=survey)
post_data = clean_post_for_status(request.POST)
form = SurveyStatusFilterForm(post_data or None, survey=survey)
if form.is_valid():
for question_id, answer_id, value in form.filters():
answer = get_object_or_404(
SurveyQuestionAnswer, id=answer_id, survey_question__id=question_id
)
if value == "none":
continue
if value == "yes":
answers_query = answers_query.filter(answers__id__exact=answer.id)
elif value == "no":
answers_query = answers_query.exclude(answers__id__exact=answer.id)
user_answers = answers_query.prefetch_related("user").all()
questions = SurveyQuestion.objects.filter(survey=survey).all()
answers_count = {}
for question in questions:
for answer in question.answers.all():
answers_count[answer.id] = 0
for user_answer in user_answers:
for answer in user_answer.answers.all():
answers_count[answer.id] += 1
return render(
request,
"survey_status.html",
{
"survey": survey,
"user_answers": user_answers,
"questions": questions,
"answers_count": answers_count,
"form": form,
},
)
@cof_required
def profile(request):
user = request.user
data = request.POST if request.method == "POST" else None
user_form = UserForm(data=data, instance=user, prefix="u")
profile_form = ProfileForm(data=data, instance=user.profile, prefix="p")
if request.method == "POST":
if user_form.is_valid() and profile_form.is_valid():
user_form.save()
profile_form.save()
messages.success(request, _("Votre profil a été mis à jour avec succès !"))
context = {"user_form": user_form, "profile_form": profile_form}
return render(request, "gestioncof/profile.html", context)
def registration_set_ro_fields(user_form, profile_form):
user_form.fields["username"].widget.attrs["readonly"] = True
profile_form.fields["login_clipper"].widget.attrs["readonly"] = True
@buro_required
def registration_form2(request, login_clipper=None, username=None, fullname=None):
events = Event.objects.filter(old=False).all()
member = None
if login_clipper:
try: # check if the given user is already registered
member = User.objects.get(username=login_clipper)
username = member.username
login_clipper = None
except User.DoesNotExist:
# new user, but prefill
# user
user_form = RegistrationUserForm(
initial={
"username": login_clipper,
"email": "%s@clipper.ens.fr" % login_clipper,
}
)
if fullname:
bits = fullname.split(" ")
user_form.fields["first_name"].initial = bits[0]
if len(bits) > 1:
user_form.fields["last_name"].initial = " ".join(bits[1:])
# profile
profile_form = RegistrationProfileForm(
initial={"login_clipper": login_clipper}
)
registration_set_ro_fields(user_form, profile_form)
# events & clubs
event_formset = EventFormset(events=events, prefix="events")
clubs_form = ClubsForm()
if username:
member = get_object_or_404(User, username=username)
(profile, _) = CofProfile.objects.get_or_create(user=member)
# already existing, prefill
user_form = RegistrationUserForm(instance=member)
profile_form = RegistrationProfileForm(instance=profile)
registration_set_ro_fields(user_form, profile_form)
# events
current_registrations = []
for event in events:
try:
current_registrations.append(
EventRegistration.objects.get(user=member, event=event)
)
except EventRegistration.DoesNotExist:
current_registrations.append(None)
event_formset = EventFormset(
events=events, prefix="events", current_registrations=current_registrations
)
# Clubs
clubs_form = ClubsForm(initial={"clubs": member.clubs.all()})
elif not login_clipper:
# new user
user_form = RegistrationPassUserForm()
profile_form = RegistrationProfileForm()
event_formset = EventFormset(events=events, prefix="events")
clubs_form = ClubsForm()
return render(
request,
"gestioncof/registration_form.html",
{
"member": member,
"login_clipper": login_clipper,
"user_form": user_form,
"profile_form": profile_form,
"event_formset": event_formset,
"clubs_form": clubs_form,
},
)
@buro_required
def registration(request):
if request.POST:
request_dict = request.POST.copy()
member = None
login_clipper = None
# -----
# Remplissage des formulaires
# -----
if "password1" in request_dict or "password2" in request_dict:
user_form = RegistrationPassUserForm(request_dict)
else:
user_form = RegistrationUserForm(request_dict)
profile_form = RegistrationProfileForm(request_dict)
clubs_form = ClubsForm(request_dict)
events = Event.objects.filter(old=False).all()
event_formset = EventFormset(events=events, data=request_dict, prefix="events")
if "user_exists" in request_dict and request_dict["user_exists"]:
username = request_dict["username"]
try:
member = User.objects.get(username=username)
user_form = RegistrationUserForm(request_dict, instance=member)
if member.profile.login_clipper:
login_clipper = member.profile.login_clipper
except User.DoesNotExist:
pass
else:
pass
# -----
# Validation des formulaires
# -----
if user_form.is_valid():
member = user_form.save()
profile, _ = CofProfile.objects.get_or_create(user=member)
was_cof = profile.is_cof
# Maintenant on remplit le formulaire de profil
profile_form = RegistrationProfileForm(request_dict, instance=profile)
if (
profile_form.is_valid()
and event_formset.is_valid()
and clubs_form.is_valid()
):
# Enregistrement du profil
profile = profile_form.save()
if profile.is_cof and not was_cof:
send_custom_mail(
"welcome",
"cof@ens.fr",
[member.email],
context={"member": member},
)
# Enregistrement des inscriptions aux événements
for form in event_formset:
if "status" not in form.cleaned_data:
form.cleaned_data["status"] = "no"
if form.cleaned_data["status"] == "no":
try:
current_registration = EventRegistration.objects.get(
user=member, event=form.event
)
current_registration.delete()
except EventRegistration.DoesNotExist:
pass
continue
all_choices = get_event_form_choices(form.event, form)
(
current_registration,
created_reg,
) = EventRegistration.objects.get_or_create(
user=member, event=form.event
)
update_event_form_comments(form.event, form, current_registration)
current_registration.options = all_choices
current_registration.paid = form.cleaned_data["status"] == "paid"
current_registration.save()
# if form.event.title == "Mega 15" and created_reg:
# field = EventCommentField.objects.get(
# event=form.event, name="Commentaires")
# try:
# comments = EventCommentValue.objects.get(
# commentfield=field,
# registration=current_registration).content
# except EventCommentValue.DoesNotExist:
# comments = field.default
# FIXME : il faut faire quelque chose de propre ici,
# par exemple écrire un mail générique pour
# l'inscription aux événements et/ou donner la
# possibilité d'associer un mail aux événements
# send_custom_mail(...)
# Enregistrement des inscriptions aux clubs
member.clubs.clear()
for club in clubs_form.cleaned_data["clubs"]:
club.membres.add(member)
club.save()
# ---
# Success
# ---
msg = (
"L'inscription de {:s} (<tt>{:s}</tt>) a été "
"enregistrée avec succès.".format(
member.get_full_name(), member.email
)
)
if profile.is_cof:
msg += "\nIl est désormais membre du COF n°{:d} !".format(
member.profile.id
)
messages.success(request, msg, extra_tags="safe")
return render(
request,
"gestioncof/registration_post.html",
{
"user_form": user_form,
"profile_form": profile_form,
"member": member,
"login_clipper": login_clipper,
"event_formset": event_formset,
"clubs_form": clubs_form,
},
)
else:
return render(request, "registration.html")
# -----
# Clubs
# -----
@login_required
def membres_club(request, name):
# Vérification des permissions : l'utilisateur doit être membre du burô
# ou respo du club.
user = request.user
club = get_object_or_404(Club, name=name)
if not request.user.profile.is_buro and club not in user.clubs_geres.all():
return HttpResponseForbidden("<h1>Permission denied</h1>")
members_no_respo = club.membres.exclude(clubs_geres=club).all()
return render(
request,
"membres_clubs.html",
{"club": club, "members_no_respo": members_no_respo},
)
@buro_required
def change_respo(request, club_name, user_id):
club = get_object_or_404(Club, name=club_name)
user = get_object_or_404(User, id=user_id)
if user in club.respos.all():
club.respos.remove(user)
elif user in club.membres.all():
club.respos.add(user)
else:
raise Http404
return redirect("membres-club", name=club_name)
@cof_required
def liste_clubs(request):
clubs = Club.objects
if request.user.profile.is_buro:
data = {"owned_clubs": clubs.all()}
else:
data = {
"owned_clubs": request.user.clubs_geres.all(),
"other_clubs": clubs.exclude(respos=request.user),
}
return render(request, "liste_clubs.html", data)
@buro_required
def export_members(request):
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = "attachment; filename=membres_cof.csv"
writer = unicodecsv.writer(response)
for profile in CofProfile.objects.filter(is_cof=True).all():
user = profile.user
bits = [
user.id,
user.username,
user.first_name,
user.last_name,
user.email,
profile.phone,
profile.occupation,
profile.departement,
profile.type_cotiz,
]
writer.writerow([str(bit) for bit in bits])
return response
# ----------------------------------------
# Début des exports Mega machins hardcodés
# ----------------------------------------
MEGA_YEAR = 2018
MEGA_EVENT_NAME = "MEGA 2018"
MEGA_COMMENTFIELD_NAME = "Commentaires"
MEGA_CONSCRITORGAFIELD_NAME = "Orga ? Conscrit ?"
MEGA_CONSCRIT = "Conscrit"
MEGA_ORGA = "Orga"
def csv_export_mega(filename, qs):
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = "attachment; filename=" + filename
writer = unicodecsv.writer(response)
for reg in qs.all():
user = reg.user
profile = user.profile
comments = "---".join([comment.content for comment in reg.comments.all()])
bits = [
user.username,
user.first_name,
user.last_name,
user.email,
profile.phone,
user.id,
profile.comments if profile.comments else "",
comments,
]
writer.writerow([str(bit) for bit in bits])
return response
@buro_required
def export_mega_remarksonly(request):
filename = "remarques_mega_{}.csv".format(MEGA_YEAR)
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = "attachment; filename=" + filename
writer = unicodecsv.writer(response)
event = Event.objects.get(title=MEGA_EVENT_NAME)
commentfield = event.commentfields.get(name=MEGA_COMMENTFIELD_NAME)
for val in commentfield.values.all():
reg = val.registration
user = reg.user
profile = user.profile
bits = [
user.username,
user.first_name,
user.last_name,
user.email,
profile.phone,
profile.id,
profile.comments,
val.content,
]
writer.writerow([str(bit) for bit in bits])
return response
# @buro_required
# def export_mega_bytype(request, type):
# types = {"orga-actif": "Orga élève",
# "orga-branleur": "Orga étudiant",
# "conscrit-eleve": "Conscrit élève",
# "conscrit-etudiant": "Conscrit étudiant"}
#
# if type not in types:
# raise Http404
#
# event = Event.objects.get(title="MEGA 2017")
# type_option = event.options.get(name="Type")
# participant_type = type_option.choices.get(value=types[type]).id
# qs = EventRegistration.objects.filter(event=event).filter(
# options__id__exact=participant_type)
# return csv_export_mega(type + '_mega_2017.csv', qs)
@buro_required
def export_mega_orgas(request):
event = Event.objects.get(title=MEGA_EVENT_NAME)
type_option = event.options.get(name=MEGA_CONSCRITORGAFIELD_NAME)
participant_type = type_option.choices.get(value=MEGA_ORGA).id
qs = EventRegistration.objects.filter(event=event).filter(
options__id=participant_type
)
return csv_export_mega("orgas_mega_{}.csv".format(MEGA_YEAR), qs)
@buro_required
def export_mega_participants(request):
event = Event.objects.get(title=MEGA_EVENT_NAME)
type_option = event.options.get(name=MEGA_CONSCRITORGAFIELD_NAME)
participant_type = type_option.choices.get(value=MEGA_CONSCRIT).id
qs = EventRegistration.objects.filter(event=event).filter(
options__id=participant_type
)
return csv_export_mega("conscrits_mega_{}.csv".format(MEGA_YEAR), qs)
@buro_required
def export_mega(request):
event = Event.objects.filter(title=MEGA_EVENT_NAME)
qs = EventRegistration.objects.filter(event=event).order_by("user__username")
return csv_export_mega("all_mega_{}.csv".format(MEGA_YEAR), qs)
# ------------------------------
# Fin des exports Mega hardcodés
# ------------------------------
@buro_required
def utile_cof(request):
return render(request, "gestioncof/utile_cof.html", {})
@buro_required
def utile_bda(request):
tirages = Tirage.objects.all()
return render(request, "utile_bda.html", {"tirages": tirages})
@buro_required
def liste_bdadiff(request):
titre = "BdA diffusion"
personnes = CofProfile.objects.filter(mailing_bda=True, is_cof=True).all()
return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes})
@buro_required
def liste_bdarevente(request):
titre = "BdA revente"
personnes = CofProfile.objects.filter(mailing_bda_revente=True, is_cof=True).all()
return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes})
@buro_required
def liste_diffcof(request):
titre = "Diffusion COF"
personnes = CofProfile.objects.filter(mailing_cof=True, is_cof=True).all()
return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes})
@cof_required
def calendar(request):
try:
instance = CalendarSubscription.objects.get(user=request.user)
except CalendarSubscription.DoesNotExist:
instance = None
if request.method == "POST":
form = CalendarForm(request.POST, instance=instance)
if form.is_valid():
subscription = form.save(commit=False)
if instance is None:
subscription.user = request.user
subscription.token = uuid.uuid4()
subscription.save()
form.save_m2m()
messages.success(request, "Calendrier mis à jour avec succès.")
return render(
request,
"gestioncof/calendar_subscription.html",
{"form": form, "token": str(subscription.token)},
)
else:
messages.error(request, "Formulaire incorrect.")
return render(
request, "gestioncof/calendar_subscription.html", {"form": form}
)
else:
return render(
request,
"gestioncof/calendar_subscription.html",
{
"form": CalendarForm(instance=instance),
"token": instance.token if instance else None,
},
)
def calendar_ics(request, token):
subscription = get_object_or_404(CalendarSubscription, token=token)
shows = subscription.other_shows.all()
if subscription.subscribe_to_my_shows:
shows |= Spectacle.objects.filter(
attribues__participant__user=subscription.user, tirage__active=True
)
shows = shows.distinct()
vcal = Calendar()
site = Site.objects.get_current()
for show in shows:
vevent = Vevent()
vevent.add("dtstart", show.date)
vevent.add("dtend", show.date + timedelta(seconds=7200))
vevent.add("summary", show.title)
vevent.add("location", show.location.name)
vevent.add(
"uid", "show-{:d}-{:d}@{:s}".format(show.pk, show.tirage_id, site.domain)
)
vcal.add_component(vevent)
if subscription.subscribe_to_events:
for event in Event.objects.filter(old=False).all():
vevent = Vevent()
vevent.add("dtstart", event.start_date)
vevent.add("dtend", event.end_date)
vevent.add("summary", event.title)
vevent.add("location", event.location)
vevent.add("description", event.description)
vevent.add("uid", "event-{:d}@{:s}".format(event.pk, site.domain))
vcal.add_component(vevent)
response = HttpResponse(content=vcal.to_ical())
response["Content-Type"] = "text/calendar"
return response
class ConfigUpdate(FormView):
form_class = GestioncofConfigForm
template_name = "gestioncof/banner_update.html"
success_url = reverse_lazy("home")
def dispatch(self, request, *args, **kwargs):
if request.user is None or not request.user.is_superuser:
return redirect_to_login(request.get_full_path())
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
form.save()
return super().form_valid(form)
##
# Autocomplete views
#
# https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#create-an-autocomplete-view
##
class UserAutocomplete(Select2QuerySetView):
model = User
search_fields = ("username", "first_name", "last_name")
user_autocomplete = buro_required(UserAutocomplete.as_view())