I am attempting to pass a slug to a form, so that it can match user accounts with related groups (called 'events' in this project/context). The slug is an identifier for the event, which has several types of many-to-many connections to user-profiles (an extension to the standard django user model specifically made for connecting users to events).
The form is supposed to create two drop-down choice fields to let an event manager set the permissions any other user has in the event, i.e. being a passive observer, an editor or a manager. However, as of now, django generates the error
'str' object has no attribute 'get'
when rendering the page. The exception is located in django/forms/widgets.py in the function value_from_datadict, with the error being stated to be on line 0 in the base template base.dashboard.html, occurring during rendering.
I've managed to avoid the error by removing both fields but leaving the slug and by removing both the candidate field and the slug, leaving the permissions field intact. I have also attempted to explicitly convert the passed value into a slug in the form (just in case some weird dynamic typing tripped the system up), but to no effect.
What am I missing?
I am using Python 3.9, Django 3.2.5 and django-crispy-forms 1.13
views.py
@login_required
def members(request, slug):
current_event = Event.objects.get(slug=slug)
member_query = (Q(is_observer=current_event) | Q(is_editor=current_event) | Q(is_manager=current_event))
current_event_members = Profile.objects.filter(member_query)
if request.method == 'POST':
form = EventMemberForm(request.POST, current_event.slug)
if form.is_valid():
if form.membership_level and form.candidate:
target = Profile.objects.get(user__username=form.candidate)
if form.membership_level == "observer":
target.is_observer.add(current_event)
target.is_editor.remove(current_event)
target.is_manager.remove(current_event)
if form.membership_level == "editor":
target.is_observer.remove(current_event)
target.is_editor.add(current_event)
target.is_manager.remove(current_event)
if form.membership_level == "manager":
target.is_observer.remove(current_event)
target.is_editor.remove(current_event)
target.is_manager.add(current_event)
if form.membership_level == "remove":
target.is_observer.remove(current_event)
target.is_editor.remove(current_event)
target.is_manager.remove(current_event)
return HttpResponseRedirect("/e/list")
else:
form = EventMemberForm(current_event.slug)
args = {}
args.update({"event": current_event})
args.update({"event_members": current_event_members})
args.update({"form": form})
return render(request, "management/event_members.html", args)
forms.py
class EventMemberForm(forms.Form):
MEMBERSHIP_LEVELS = [
('observer', 'Observer'),
('editor', 'Editor'),
('manager', 'Manager')
]
membership_level = forms.ChoiceField(label='Permissions:', choices=MEMBERSHIP_LEVELS, required=False)
candidate = forms.ModelChoiceField(queryset=Profile.objects.none(), label="Candidate:", required=False)
class Meta:
fields = ['membership_level', 'candidate']
def __init__(self, *args, **kwargs):
super(EventMemberForm, self).__init__(*args, **kwargs)
selected_event = Event.objects.get(slug=args[0])
self.fields['candidate'].queryset = Profile.objects.filter((~Q(is_observer=selected_event) | ~Q(is_editor=selected_event)) | ~Q(is_manager=selected_event) & Q(user__is_active=1)).order_by("user__username")
template (event_members.html)
{% extends "base.dashboard.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Members in Event "{{ event.name }}"</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<a href="{% url 'Event_List' %}" class="btn btn-sm btn-outline-secondary">List of Events</a>
</div>
</div>
</div>
<h2>Members</h2>
{% if event_members|length > 0 %}
{% if request.user.profile.is_manager %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
<a class="close" data-dismiss="alert">×</a>
{% for non_field_error in form.non_field_errors %}
{{ non_field_error }}
{% endfor %}
</div>
{% endif %}
<form role="form" action="{% url 'Event_Members' slug=event.slug %}" method="post">
{% csrf_token %}
<div class="form-group">
{{ form|crispy }}
<button type="submit" class="btn btn-sm btn-outline-secondary">Change permissions</button>
</div>
</form>
{% endif %}
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Permissions</th>
</tr>
</thead>
<tbody>
{% for member in event_members %}
<tr>
<td><strong>{{ member.user.username }}</strong></td>
<td>
{% if member.is_manager %}
Manager
{% elif member.is_editor %}
Editor
{% elif member.is_observer %}
Observer
{% else %}
Error
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-danger">Error: No members found. Contact an administrator!</div>
{% endif %}
{% endblock %}
models.py (Profile)
class Profile(models.Model):
user = models.OneToOneField(User, related_name="profile", on_delete=models.CASCADE)
is_observer = models.ManyToManyField(Event, related_name="event_observer", blank=True)
is_editor = models.ManyToManyField(Event, related_name="event_editor", blank=True)
is_manager = models.ManyToManyField(Event, related_name="event_manager", blank=True)
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
models.py (Event)
class Event(models.Model):
name = models.CharField(verbose_name="Name", max_length=50)
date = models.DateField(verbose_name="Starting Date (DD.MM.YYYY)")
description = models.TextField(verbose_name="Description")
slug = models.SlugField(max_length=10)
def __str__(self):
return str(self.name)
def save(self, *args, **kwargs):
if not self.id: # skip if event already exists
if get_or_none(Event, slug=slugify(self.name[:10])) is None: # check for duplicate slugs
self.slug = slugify(self.name[:10])
else: # iteratively append integer to slug candidates until an unused slug is found
i = 0
finished = False
while not finished:
slug_candidate = slugify((self.name[:10-len(str(i))] + str(i)))
if get_or_none(Event, slug=slug_candidate) is None:
self.slug = slug_candidate
finished = True
else:
i = i + 1
if get_or_none(Event, name=self.name): # check for duplicate name and generate new one if needed (compare above)
i = 0
finished = False
while not finished:
name_candidate = str(self.name[:50 - len(str(i))] + str(i))
if get_or_none(Event, name=name_candidate) is None:
self.name = name_candidate
finished = True
else:
i = i + 1
super(Event, self).save(*args, **kwargs)