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 >
<h1 >Members in Event "{{ event.name }}"</h1>
<div >
<div >
<a href="{% url 'Event_List' %}" >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 >
<a 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 >
{{ form|crispy }}
<button type="submit" >Change permissions</button>
</div>
</form>
{% endif %}
<div >
<table >
<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 >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)
CodePudding user response:
in your EventMemberForm __init__() you call super.__init__() and pass all arguments:
class EventMemberForm(forms.Form):
...
def __init__(self, *args, **kwargs):
super(EventMemberForm, self).__init__(*args, **kwargs)
BaseForm from https://github.com/django/django/blob/main/django/forms:/forms.py
Class BaseForm():
....
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None):
in your case super would be called with current_event.slug as first positional argument which goes to "data" in BaseForm Class. Most certainly that is not what you want to happen.
