Creating a multiplechoice field using many to many relationship - django

Im trying to add a field called, interested_fields inside my personalInfo model which users can choose from and the choices themselves come from another models' objects with the help of ManyToMany relation between the two models. Here are my models.py codes(I simplified my personal model by removing some other fields like name, age, etc in order to make it more readable for you):
class Field(models.Model):
id = models.AutoField(primary_key=True)
slug = models.CharField(max_length=16, default='default')
title = CharField(max_length=32)
class PersonalInfo(models.Model):
id = models.AutoField(primary_key=True)
interested_fields = models.ManyToManyField(Field, blank=True)
then, I created a ModelForm like this:
class InterestedFieldsForm(forms.ModelForm):
interested_fields = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=Field.objects.all(), required=False)
class Meta:
model = PersonalInfo
fields = ['interested_fields']
and created a get and post functions inside my views like this:
class PersonalView(View):
template_name = 'reg/personal.html'
def get(self, request, *args, **kwargs):
context = {}
context['fields'] = Field.objects.all()
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
user = request.user
if request.method == 'POST':
form = InterestedFieldsForm(request.POST)
if form.is_valid():
profile = form.save(commit=False)
profile.user = request.user
profile.save()
else:
form = InterestedFieldsForm()
return render(request, 'reg/done.html', context={'form': form})
and finally in template, inside the form I added this for loop:
{% for field in fields %}
<label class="containerq ant-col ant-col-md-6 ant-col-xs-8" >
<span>
<input type="checkbox" name="interested_fields" {% if field.slug in user.personalInfo.interested_fields %} checked="checked" {% endif %} value="{{field.title}}">
<span style="margin-left:7px" class="checkmark"></span>
</span>
<span>{{field.title}}</span>
</label>
{% endfor %}
when I submit the form it gives me this error:
cannot unpack non-iterable Field object
Im new to django so I really dont know what am I doing wrong. thank you for your answers

You should use a ModelMultipleChoiceField
interested_fields = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset=Field.objects.all(), required=False).

Related

Django. ManagementForm data is missing or has been tampered with error

Suppose, i have two models:
Model Basket
Model Apple
One basket can have many apples in it, so there is one-to-many relationship between Basket and Apple.
For example, here is my models:
class Basket(models.Model):
backet_name = models.CharField(max_length=150)
owner = author = models.ForeignKey(get_user_model(), on_delete = models.CASCADE)
class Apple(models.Model):
apple_sort = models.CharField(max_length=150)
apple_size = models.CharField(max_length=150)
basket = models.ForeignKey(Basket, on_delete=models.CASCADE, related_name="apple_in_basket")
I want to create a page to add basket and then to add apples to it at the same page, so i created a following forms, using formsets:
class NewBasketForm(ModelForm):
class Meta:
model = Basket
exclude = ['owner']
class AddAppleToBasket(ModelForm):
class Meta:
model = Apple
exclude = ['basket']
AppleFormSet = modelformset_factory(Apple, form=AddAppleToBasket, extra=1, max_num=10)
I want my apples to automaticly set foreign key to basket that i just created.
So i created a view to insert Basket and Apple objects to the database:
class BascketAddView(View):
form_class_for_basket = NewBasketForm
form_class_for_apples = AppleFormSet
template_name = 'buskets/add.html'
def get(self, request, *args, **kwargs):
basket_form = self.form_class_for_apples()
apples_form = self.form_class_for_answers()
return render(request, self.template_name, {'basket_form': basket_form, 'apples_form': apples_form})
def post(self, request, *args, **kwargs):
basket_form = self.form_class_for_basket(request.POST)
apple_form = self.form_class_for_apples(request.POST)
if basket_form.is_valid() and apple_form.is_valid():
print("form is valid!!!")
new_basket = basket_form.save(commit=False)
new_basket.owner = request.user
new_basket.save()
instances = apple_form.save(commit=False)
for instance in instances:
instance.basket = new_basket
instance.save()
return HttpResponseRedirect('/')
print("form is invalid")
return render(request, self.template_name, {'basket_form': busket_form, 'apples_form': apples_form})
The form is rendering just fine, but when i fill it and submit i get "form is invalid" in the console.
What am i doing wrong?
In template, {{ apple_form.non_form_error }} gives me following error:
ManagementForm data is missing or has been tampered with. Missing fields: form-TOTAL_FORMS, form-INITIAL_FORMS. If the issue persists, you may need to file a bug report.
I found the solution!
Well, if anyone will stumble оn this type of error, try adding {{ formset.management_form }} in your template where form is rendered, inside <form method="post> tag:
So, in my case i should add:
<form method="post" enctype='multipart/form-data'>
{{ apple_form.management_form }}
{% for form in apple_form %}
</form>
More info in the Django doc: https://docs.djangoproject.com/en/4.1/topics/forms/formsets/

Django Dynamic Forms ManyToManyField

I have a problem that I want to make a football league page that can submit the starting XI list.
But I don't know how to build the Dynamic Form.
User (which is the team owner) can Login to choose the match to upload the starting XI.
Website will show up the player list of the Team and let the User select Max of 11 and submit.
The important thing is
How can I generate the form with the Players belongs to Team.
How to let the User upload Home / Away (The team belongs to User) starting XI only.
Model.py
class Schedule(models.Model):
schedule_name = models.CharField(max_length=7, choices=LEAGUE_CHOICES, default='nil')
schedule_home = models.ForeignKey(Team, on_delete=models.CASCADE,default='',related_name='schedule_home')
schedule_away = models.ForeignKey(Team, on_delete=models.CASCADE,default='',related_name='schedule_away')
class Player(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
player_name = models.CharField('Player Name', max_length=30, unique=True)
player_team = models.ForeignKey(Team ,on_delete=models.SET_DEFAULT, default=1)
class Team(models.Model):
team_name = models.CharField('Team Name', max_length=30, unique=True)
team_owner = models.OneToOneField(User,on_delete=models.CASCADE,related_name='owner')
class Match_Starting(models.Model):
starting_schedule = models.OneToOneField(Schedule,on_delete=models.CASCADE)
home_starting = models.ManyToManyField(Player,blank=True,related_name='home_starting')
away_starting = models.ManyToManyField(Player,blank=True,related_name='away_starting')
#receiver(post_save, sender=Schedule)
def create_match_stat(sender, instance, created, **kwargs):
if created:
Match_Starting.objects.create(starting_schedule=instance)
Forms.py
class MatchStartingForm(forms.ModelForm):
class Meta:
model = Match_Starting
fields = '__all__'
Views.py
#login_required
def update_starting(request):
if request.user.is_authenticated:
try:
selected_team = Team.objects.get(team_owner=request.user)
except:
return HttpResponseRedirect('/')
if request.method == 'GET':
selected_player = Player.objects.filter(player_team=selected_team).order_by('player_name')
team_schedule_list = Schedule.objects.filter(schedule_time__lte=datetime.datetime.now() + datetime.timedelta(weeks=1),schedule_time__gte=datetime.datetime.now() + datetime.timedelta(minutes=30)).filter(Q(schedule_home__team_name=selected_team)|Q(schedule_away__team_name=selected_team))
return render(request, 'update_starting/update_starting.html',{"user":request.user,"team":selected_team,"player":selected_player,"schedule":team_schedule_list})
if request.method == 'POST':
selected_match= get_object_or_404(Match_Starting, starting_schedule__id=request.POST.get("schedule_id"))
request.POST = request.POST.copy()
if selected_team == selected_match.starting_schedule.schedule_home:
request.POST['home_starting'] = request.POST['starting']
form = MatchStartingForm(request.POST, instance=selected_match)
if form.is_valid():
selected_match = form.save(commit=False)
selected_match.save()
form.save_m2m()
elif selected_team == selected_match.starting_schedule.schedule_away:
request.POST['away_starting'] = request.POST['starting']
form = MatchStartingForm(request.POST, instance=selected_match)
if form.is_valid():
selected_match = form.save(commit=False)
selected_match.save()
form.save_m2m()
return HttpResponseRedirect('/')
update_starting.html
<div>
<form action="" method="post">
{% csrf_token %}
<select name="schedule_id">
{% for x in schedule %}
<option value="{{x.id}}">{{x.get_schedule_name_display}} {{x.schedule_home}} - {{x.schedule_away}}</option>
{% endfor %}
</select>
{% for x in player %}
<input type="checkbox" name="starting" value="{{x.id}}">{{x.player_name}}<br>
{% endfor %}
<input type="submit" class="button" value="Save">
</form>
</div>
How can I generate the form with the Players belongs to Team.
You first would need a form for players:
class PlayerForm(forms.ModelForm):
class Meta:
model = Player
fields = '__all__'
and then put this into practice: Django Formsets
How to let the User upload Home / Away (The team belongs to User) starting XI only.
You will need here a form too, this time for the Schedule model as well.
#rchurch4 said: They just want to know how to ensure that the User can only upload the roster for his team.
If that the case, you only have to be sure in your Team list view you only list teams belonging to the authenticated User.

Django: DetailView containing reviews. Add a form for reviews with update/create

Currently I have the following structure.
I have Users, which can be Teachers or Students. Students can leave reviews for teachers. I've set up a detailed view for teachers and added a 'reviews' attribute to get_context_data to loop through reviews to display them.
Aim: Each user who is a student can submit a review of a teacher. I want to display a form at the bottom of my detailed view. If user already had a review, then we call update. If user doesn't have a review, we create it.
Models.py
class Teacher(models.Model):
user = models.OneToOneField(User, on_delete=models.PROTECT, related_name='Teacher')
availability = models.BooleanField(default=False)
def __str__(self):
return self.user.username
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.PROTECT, related_name='Student')
teacher = models.ForeignKey(Teacher)
reviewed = models.BooleanField(default=False)
def __str__(self):
return self.user.username
class Review(models.Model):
teacher = models.ForeignKey(Teacher)
student = models.OneToOneField(Student, on_delete=models.PROTECT, related_name='Student')
star = models.IntegerField(default=5)
body = models.TextField()
Views.py
class ReviewSubmitForm(forms.Form):
star = forms.IntegerField()
body = forms.CharField()
class TeacherView(generic.DetailView):
model = Teacher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(TeacherView, self).get_context_data(**kwargs)
# Add extra context from another model
context['reviews'] = Review.objects.filter(teacher_id=self.kwargs['pk'])
context['rating'] = Review.objects.filter(teacher_id=self.kwargs['pk']).aggregate(Avg('star'))
context['form'] = ReviewSubmitForm()
return context
class ReviewSubmit(SingleObjectMixin, FormView):
template_name = 'users/teacher_detail.html'
form_class = ReviewSubmitForm
model = Review
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
return super(ReviewSubmit, self).post(request, *args, **kwargs)
def get_success_url(self):
return reverse('users:detail', kwargs={'pk': self.object.pk})
I'm now able to see the form and use it in my template.
My teacher view is located at /users/<pk>
The logic that I'd like is: look for , check current student id.
If there is an entry with pk=pk and student_id = student_id then load update form. Otherwise if it's a student load create form.
This example is somewhat similar, but not quite the same.
Edit: form_template.html
{% for field in form %}
<div class="form-group">
<span color="red">{{ field.errors }}</span>
<label>
{{ field.label_tag }}
</label>
<div>{{ field }}</div>
</div>
{% endfor %}

Django set ModelForm field without including it in the form

In my app, I have Users create Post objects. Each Post has a User
class Post(models.Model):
user = models.ForeignKey(User, on_delete = models.CASCADE)
...
I want to create a post-submission form for editing and submission, so I plan to use Django's ModelForm functionality.
class PostForm(ModelForm):
class Meta:
model = Post
fields = "__all__"
However, if I do this, then whoever is viewing the form will be able to set who the Post author is. I want to make sure that the resulting user field is them. But, if I exclude the user field from the ModelForm,
class PostForm(ModelForm):
class Meta:
model = Post
exclude = 'user'
then the user will not be set on form submission. I've hacked my way around this by making a custom form and updating the post field
def submit_view(request):
....
request.POST = request.POST.copy()
request.POST.update({
'user' : request.user.id
})
form = PostForm(request.POST, request.FILES)
....
but then I lose automatic UI generation and form validation, which in some ways defeats the purpose of the Form class. Could somebody point me to the idiomatic way of setting the user field without including it in the Form?
Try this view:
def submit_view(request):
form = PostForm(request.POST or None)
if form.is_valid():
new_post = form.save(commit=False)
new_post.user = request.user
new_post.save()
view.py
from django.views.generic import CreateView
from .models import Post
class PostCreate(CreateView):
model = Post
template_name ="new_Post_form.html"
fields = ['text']
def form_valid(self, form):
object = form.save(commit=False)
object.user = self.request.user
object.save()
return super(PostCreate, self).form_valid(form)
def get_success_url(self):
return "/"
url.py
url(r'^newpost$',views.PostCreate.as_view(),name='post_new',),
new_post_form.html
<form method="post" enctype="multipart/form-data" class="form" action="newpost" id="new-post-form">
<div class="modal-body">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</div>

Django 'QuerySet' object has no attribute 'split' using modelform

I'm having a problem getting my view to update a manytomany field. It returns this after the form is submitted.
Traceback:
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py" in get_response
111. response = callback(request, *callback_args, **callback_kwargs)
File "/home/footbook/Ubuntu One/webapps/fb/poc/../poc/activity/views.py" in activity_save_page
44. group_names = form.cleaned_data['groups'].split()
Exception Type: AttributeError at /activity_save/
Exception Value: 'QuerySet' object has no attribute 'split'
Here are the files.
Models.py
class Group (models.Model):
group_nm = models.CharField(max_length=64)
group_desc = models.CharField(max_length=250)
created = models.DateTimeField(auto_now_add=True)
active_yn = models.BooleanField(default=True)
def __unicode__(self):
return self.group_nm
class Activity(models.Model):
activity_nm = models.CharField(max_length=60)
activity_desc = models.CharField(max_length=250)
startdt = models.DateField()
enddt = models.DateField()
crdt = models.DateTimeField(auto_now_add=True,editable=False)
groups = models.ManyToManyField(Group)
upddt = models.DateTimeField(editable=False)
def save(self, *args, **kwargs):
if not self.id:
self.crdt = datetime.date.today()
self.upddt = datetime.datetime.today()
super(Activity, self).save(*args, **kwargs)
def __unicode__(self):
return self.name
forms.py
def make_custom_datefield(f):
formfield = f.formfield()
if isinstance(f, models.DateField):
formfield.widget.format = '%m/%d/%Y'
formfield.widget.attrs.update({'class':'datePicker', 'readonly':'true'})
return formfield
class ActivitySaveForm(forms.ModelForm):
formfield_callback = make_custom_datefield
def __init__(self, *args, **kwargs):
super(ActivitySaveForm, self).__init__(*args, **kwargs)
self.fields['activity_nm'].label = "Activity Name"
self.fields['activity_desc'].label = "Describe It"
self.fields['startdt'].label = "Start Date"
self.fields['enddt'].label = "End Date"
self.fields['groups'].label ="Group"
class Meta:
model = Activity
views.py
def activity_save_page(request):
if request.method == 'POST':
form = ActivitySaveForm(request.POST)
if form.is_valid():
act, created = Activity.objects.get_or_create(
activity_nm = form.cleaned_data['activity_nm']
)
act.activity_desc = form.cleaned_data['activity_desc']
if not created:
act.group_set.clear()
group_names = form.cleaned_data['groups'].split()
for group_name in group_names:
group, dummy = Group.objects.get_or_create(group_nm=group_name)
act.group_set.add(group)
act.save()
return HttpResponseRedirect('/activity/')
else:
form = ActivitySaveForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('activity_save.html', variables)
I think it would work if I wasn't using the modelform, but I need it to implement this datepicker. Since it's a manytomany field, I want to split them when they are entered into the database, but my queryset fails. I've tried changing this a bunch of different ways, but I'm stuck. I've seen a lot of similar questions, but they either had foreign keys or no modelform.
Thanks.
EDIT:
activity_save.html
{% extends "base.html" %}
{% block title %}Save Activity{% endblock %}
{% block head %}Save Activty{% endblock %}
<input class="datePicker" readonly="true" type="text" id="id_startdt" />
<input class="datePicker" readonly="true" type="text" id="id_enddt" />
{% block content %}
<form action="{% url activity.views.activity_save_page act_id%}" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="save it" />
</form>
{% endblock %}
Exactly as the error describes: a QuerySet does not have a split method. You cannot call my_qs.split().
form.cleaned_data['groups'] returns cleaned data; it has already taken care of the form string-to-python-object conversion for you, which in the case of a ManyToManyField is ultimately represented by a QuerySet in python.
A date field returns date objects, IntegerField an integer, CharFields a string, etc. in the same way via form cleaning.
If you want a list of group_names, you'd need to explicitly iterate through the objects in the QuerySet and pull their group_nm attribute.
group_names = [x.group_nm for x in form.cleaned_data['groups']]
I'm not sure you need to do all that in your view. You can directly save the form in the view without manually creating the objects and manipulating them.
Also, you need to get the id of activity so that you can update existing activity instance.
Update the urls.py to have these urls to have act_id:
url(r'^activity_app/save/(?P<act_id>\d+)/$', 'activity_app.views.activity_save_page'),
url(r'^activity_app/save/$', 'activity_app.views.activity_save_page'),
I would change the view to:
def activity_save_page(request, act_id=None):
act_inst = None
try:
if act_id:
act_inst = Activity.objects.get(id=act_id)
except Exception:
pass
if request.method == 'POST':
form = ActivitySaveForm(request.POST, instance=act_inst)
if form.is_valid():
return HttpResponseRedirect('/activity/')
else:
form = ActivitySaveForm(instance=act_inst)
variables = RequestContext(request, {
'form': form
})
return render_to_response('activity_save.html', variables)