Django: ModelFormSet saving first entry only - django

Update:
The issue seemed to be in the coding for Django-formset. I was processing it as an inline formset and not a model formset. The answer below was also correct. Thanks!
I am working with a model formset for an intermediate model. I am using django-formset js to add additional formset fields on the template. Most everything works OK except that when I go to save the formset only the first entry is being saved to the DB. The first entry is saved and assigned correctly but any after than just disappear. It is not throwing any errors so I am not sure what is going wrong. Thanks!
The Model
class StaffAssignment(models.Model):
study = models.ForeignKey(Study, related_name='study_set', null=True, on_delete=models.CASCADE)
staff = models.ForeignKey('account.UserProfile', related_name='assigned_to_set', null=True, on_delete=models.CASCADE)
role = models.CharField(max_length=100, null=True)
assigned_on = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-role',)
def __str__(self):
return '{} is assigned to {}'.format(self.staff, self.study)
The Form:
class AddStaff(forms.ModelForm):
model = StaffAssignment
fields = ('staff',)
def __init__(self, *args, **kwargs):
super(AddStaff, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs.update({'class': 'form-control'})
The View:
def add_staff(request, study_slug):
study = get_object_or_404(Study, slug=study_slug)
staff_formset = modelformset_factory(StaffAssignment, form=AddStaff, fields=('staff',), can_delete=True)
if request.method == 'POST':
staffList = staff_formset(request.POST, request.FILES)
if staffList.is_valid():
for assignment in staffList:
assigned = assignment.save(commit=False)
assigned.study = study
assigned.role = assigned.staff.job_title
assigned.save()
return HttpResponseRedirect(reverse('studies:studydashboard'))
else:
HttpResponse('Something is messed up')
else:
staffList = staff_formset(queryset=StaffAssignment.objects.none())
return render(request, 'studies/addstaff.html', {'staffList': staffList, 'study': study})
The Template:
<form action="{% url 'studies:addstaff' study.slug %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="box-body">
{% for list in staffList %}
<div class="form-group" id="formset">
{% if list.instance.pk %}{{ list.DELETE }}{% endif %}
{{ list.staff }}
{% if list.staff.errors %}
{% for error in list.staff.errors %}
{{ error|escape }}
{% endfor %}
{% endif %}
</div>
{% endfor %}
{{ staffList.management_form }}
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>

You are not including the primary key field in the template, as required by the docs. Add
{% for list in staffList %}
{{ list.pk }}
...
{% endfor %}

Related

I created a view that add stops to route and they are not appearing by parameter order from models

I created view where i add stops to route, its working fine, but stops are not appearing in order when i add them, and i have no idea why. Please tell me where i'm making a mistake
Here's my code:
Models:
class Port(models.Model):
name = models.CharField(max_length=128)
description = models.TextField(default='')
lattitude = models.DecimalField(max_digits=9, decimal_places=6)
longitude = models.DecimalField(max_digits=9, decimal_places=6)
amenities = models.ManyToManyField(Amenity, blank=True)
def __str__(self):
return f'{self.name}'
class Route(models.Model):
name = models.CharField(max_length=128, default='')
user = models.ForeignKey(User, on_delete=models.CASCADE)
stop_list = models.ManyToManyField(Port, through='RoutePort')
def __str__(self):
return f'{self.name}'
class RoutePort(models.Model):
port = models.ForeignKey(Port, on_delete=models.CASCADE)
route = models.ForeignKey(Route, on_delete=models.CASCADE)
order = models.PositiveIntegerField()
class Meta:
ordering = ['order']
the order i intend them to appear is set by 'order' parameter from class routeport
form:
class AddRoutePort(forms.ModelForm):
class Meta:
model = RoutePort
fields = ['port', 'order']
form is short and i dont know if i didnt forget something there
View, get is working correctly, post works almost ok. New ports are correctly added to new route from list, but they appear in order of being added, not the one i wanted them to.
class RouteCorrectView(View):
def get(self, request, pk):
route = Route.objects.get(pk=pk)
form = AddRoutePort()
return render(request, 'route_correct.html', {'route':route, 'form':form})
def post(self, request, pk):
route = Route.objects.get(pk=pk)
form = AddRoutePort(request.POST)
if form.is_valid():
to_port = form.save(commit=False)
to_port.route = route
order = to_port.order
ordering = RoutePort.objects.filter(order__gte=order, route=route)
for port in ordering:
port.order += 1
port.save()
to_port.save()
return redirect('route_correct', pk)
return render(request, 'route_correct.html', {'route':route, 'form':form})
and HTML - i can't draw set order here, so i think theres a mistake somwhere previously. Line {{ stops.order }} does nothing.
{% extends 'base.html' %}
{% block content %}
<div class="card">
<ul>Nazwa</ul>
<p>{{ route.name }}</p>
<ul>Twórca</ul>
<p> {{ route.user }}</p>
<ul>Szlak</ul>
<td> {% for stops in route.stop_list.all %}
{{ stops }} {{ stops.order }}
<td>
<form action="{% url 'delete_stop' stops.id %}" method="get"> {% csrf_token %}
<input type="submit">usuń</form>
<form action="{% url 'port_detail' stops.id %}" method="get"> {% csrf_token %}
<input type="submit">o porcie</form><br>
</td>
{% empty %}
Szlak jest jeszcze pusty
{% endfor %}</td>
</div>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<input type="submit">
</form>
</div>
{% endblock %}
So i found the solution. First of all i'm an idiot, second of all - html was problem
<td> {% for stops in route.stop_list.all %}
{{ stops }} {{ stops.order }}
<td>
Above part refer to stop list, not routeport model with "order" in it. It should look like that:
<td> {% for stops in route.routeport_set.all %}
{{ stops.port }} {{ stops.order }}
<td>
where i refer to routeport with order in it.

My form keep saying "This(image) field is required!" Django 3.0

I made a project with net-ninja on youtube now I am adding "uploading media" feature in my own project but it is not working although every thing is set just tell me where i m going wrong.
My form is keep asking me to upload image file even though i had already done, every time i send a post request it renders the page again with no value in image field. what is wrong here, why it is not taking image input?Don't comment set the required to false that is not the solution i don't know why some people said this to others on stackoverflow when they asked the same question as me.
My model class looks like this
class Products(models.Model):
name = models.CharField(max_length=500, unique=True, )
price = models.IntegerField()
stock = models.IntegerField()
date_added = models.DateTimeField(verbose_name="date added", auto_now_add=True, )
thumb = models.ImageField(default='default.png', blank=True)
profile = models.ForeignKey(Profile, on_delete=models.CASCADE, default=None,)
def __str__(self):
return self.name
class Meta():
verbose_name_plural = "Products"
My form looks like this
class AddProduct(forms.ModelForm):
name = forms.CharField(widget=forms.TextInput(attrs={'placeholder':'Product Name', 'required':'True', 'class': 'form-control'}))
price = forms.IntegerField(widget=forms.NumberInput(attrs={'placeholder':'Set Price', 'required':'True', 'class': 'form-control'}))
stock = forms.IntegerField(widget=forms.NumberInput(attrs={'placeholder':'Number of items', 'required':'True', 'class': 'form-control'}))
thumb = forms.ImageField(required=False, widget=forms.ClearableFileInput(attrs={'placeholder':'Upload Picture', 'enctype' : 'multipart/form-data'}))
class Meta():
model = Products
fields = ('name', 'price', 'stock','thumb', )
HTML looks like this
<form class="row contact_form" action="/shop/add_products" method="post">
{% csrf_token %}
{% for field in form %}
<div class="col-md-12 form-group p_star">
{{ field }}
{{ field.errors }}
</div>
{% endfor %}
<!-- {% for field in form %}
{% for error in field.errors %}
<div class="col-md-12 form-group p_star">
{{ error }}
</div>
{% endfor %}
{% endfor %} -->
{% if form.non_field_errors %}
<div class="col-md-12 form-group p_star">
{{ form.non_field_errors }}
</div>
{% endif %}
<div class="col-md-12 form-group">
<button type="submit" value="submit" class="btn_3">
Add Product
</button>
</div>
</form>
My views file looks like this
#login_required(login_url='/shop/login')
def shop_add_products(request):
if request.method == 'POST':
form = AddProduct(request.POST, request.FILES)
if form.is_valid():
instance = form.save(commit=False)
instance.profile = request.user
instance.save()
return redirect("/shop/products")
else:
form = AddProduct()
return render(request, "pillow_site_html/add_product.html", { 'form':form })
Oh sorry i didn't understood the question here, you are getting that because in fact you are not sending the picture inside your form as you have to add this to your form so it can accept files handling
<form class="row contact_form" action="/shop/add_products" method="post" enctype="multipart/form-data">

How to create a django formset or form add/edit page in order to edit several records

For one of my open source projects, I need to create ONE add/edit page in order to make possible to edit several records with one save.
The repo is an IMDB clone formed for learning purpose. A user can add her/his favorite genres in her/his profile. Then an edit page is formed to show the list of those favored genres and the movies within that genre. (A for loop here) User can add notes, watch list options and so on to those movies. (NOT a FORMSET)
However, the code doesn't work as expected. The page cannot be saved and only the first checkbox of the list can be changed.
There is no error.
NOTE:
You can install repo with dummy data.
(https://github.com/pydatageek/imdb-clone)
Then after logging in, select your favorite genres. (http://localhost:8000/users/profile/)
Then (I wish it can be solved here) you can see the movies with your selected genres. Add notes, to watch list... (http://localhost:8080/users/profile/movies2/)
# users/templates/user-movies-with_loop.html
{% extends 'base.html' %}{% load crispy_forms_tags %}
<!-- Title -->
{% block htitle %}Your movies from favorite genres{% endblock %}
{% block title %}Your movies from favorite genres{% endblock %}
{% block content %}
<div class="card card-signin">
{% include 'users/profile-menu.html' %}
<h3 class="card-title text-center my-4">Take notes for your movies <small></small></h3>
<hr class="mb-1">
<div class="card-body">
<form method="POST">
{% csrf_token %}
{% for genre in user.genres.all %}
<h2 for="genre" name="genre" value="{{ genre.id }}">{{ genre.name }}</h2>
{% for movie in genre.movies.all %}
<div class="ml-5">
<h4>{{ movie.title }}</h4>
{{ form|crispy }}
</div>
<input type="hidden" name="user" value="{{ user.id }}">
<input type="hidden" name="movie" value="{{ movie.id }}">
{% empty %}
<p class="alert alert-danger">The genre you have selected on your profile doesn't have any movies!</p>
{% endfor %}
{% empty %}
<p class="alert alert-danger">You should select genres with movies from your profile to edit here!</p>
{% endfor %}
<input class="btn btn-lg btn-primary btn-block text-uppercase" type="submit" value="Submit">
</form>
</div>
</div>
{% endblock %}
# users.forms.py
...
class UserMovieFormWithLoop(ModelForm):
genre = forms.HiddenInput(attrs={'disabled': True})
class Meta:
model = UserMovie
fields = ('user', 'movie', 'note', 'watched', 'watch_list')
widgets = {
'user': forms.HiddenInput,
'movie': forms.HiddenInput,
'watched': forms.CheckboxInput(),
}
...
# users.models.py
...
class UserMovie(models.Model):
"""
Users have notes about their favorite movies.
"""
user = models.ForeignKey(User, on_delete=models.CASCADE)
movie = models.ForeignKey('movies.Movie', default=1, on_delete=models.CASCADE)
note = models.CharField(max_length=250, null=True, blank=True)
watched = models.BooleanField(default=False, verbose_name='Have you seen before?')
watch_list = models.BooleanField(default=False, verbose_name='Add to Watch List?')
def __str__(self):
return f'{self.user.username} ({self.movie.title})'
...
# users.views.py
...
class UserMovieViewWithLoop(LoginRequiredMixin, CreateView):
model = UserMovie
template_name = 'users/user-movies-with_loop.html'
form_class = UserMovieFormWithLoop
success_message = 'your form has been submitted.'
success_url = reverse_lazy('users:user_movies2')
def form_valid(self, form):
user = self.request.user
movie_counter = Movie.objects.filter(genres__in=user.genres.all()).count()
f = form.save(commit=False)
f.user = user
for i in range(movie_counter):
f.pk = None
f.save()
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super(UserMovieViewWithLoop, self).get_context_data(**kwargs)
context['form'] = self.form_class
return context
def get_object(self):
user = self.request.user
return UserMovie.objects.get(user=user)
...

Why the attribute of an instance model does not change

I create a financial manager and I need the user to change the account activity (indicate an active account or not), but when I send the form for change, then the model attribute does not change and always remains TRUE
I also tried to do this through a copy of the Account, but it was also not a result of the outcome
Account.objects.get(user=self.request.user, id=id).is_active = False
models.py
class Account(models.Model):
type_of_currency = models.CharField(max_length=20)
user = models.ForeignKey(get_user_model(), blank=True,
related_name='user_account',
on_delete=models.CASCADE)
count = models.DecimalField(max_digits=12, decimal_places=2, blank=True)
created = models.DateTimeField(default=datetime.datetime.now)
is_active = models.BooleanField()
def __str__(self):
return f'{self.type_of_currency} - {self.count}'
views.py
class AccountDetailView(DetailView, UpdateView):
model = Account
form_class = AccountCreateForm
template_name = 'account_detail.html'
def post(self, request, *args, **kwargs):
id = self.request.POST['accountid']
self.request.user.user_account.get(id=6).is_active = False
print(self.request.user.user_account.get(
id=id).is_active) # always True why?
return redirect('/counts/' + id)
template
{% extends 'base.html' %}
{% block content %}
<div class="col-sm-5">
<h1>account: {{ account.id }}</h1>
<p><strong>Author:</strong> {{ account.user }}</p> <!-- author detail link not yet defined -->
<p><strong>Type:</strong> {{ account.type_of_currency }}</p>
<p><strong>count:</strong> {{ account.count }}</p>
<p><strong>IsCreated:</strong> {{ account.created }}</p>
<p><strong>IsActive:</strong>{{ account.is_active }}</p>
<a class="btn btn-outline-primary"
href="{% url 'account-list' %}">Back</a>
{% if account.is_active %}
<form method="post">
{% csrf_token %}
<input type="hidden" value="{{ account.id }}" name="accountid">
<button type="submit" class="btn btn-outline-danger">Deactivate</button>
</form>
{% else %}
<form method="post">
{% csrf_token %}
<button type="submit" class="btn btn-outline-success">Activate</button>
</form>
{% endif %}
</div>
{% endblock %}
In the post method DetailViews, I expect that after the button is pressed, user activity will change, but the result is always True
You're never commiting change to DB.
Also, I wouldn't use the following syntax: self.request.user.user_account.get(id=6).is_active = False
You can try: self.request.user.user_acocount.filter(id=6).update(is_active=False)
But, if you're dead set on using .get():
user_acc = self.request.user.user_account.get(id=6)
user_acc.is_active = False
user_acc.save()

Retrieving models from form with ModelMultipleChoiceField

I am having difficulties with forms, specifically ModelMultipleChoiceField.
I've pieced together this code from various examples, but it sadly doesn't work.
I would like to be able to:
Search for some Works on work_search.html
Display the results of the search, with checkboxes next to each result
Select the Works I want, via the checkboxes
After pressing Add, display which works were selected.
I believe everything is okay except the last part. The page simply displays "works" :(
Here is the code - sorry about the length.
Models.py
class Work(models.Model):
title = models.CharField(max_length=200)
artist = models.CharField(max_length=200)
writers = models.CharField(max_length=200)
def __unicode__(self):
return self.title + ' - ' + self.artist
forms.py
class WorkSelectForm(forms.Form):
def __init__(self, queryset, *args, **kwargs):
super(WorkSelectForm, self).__init__(*args, **kwargs)
self.fields['works'] = forms.ModelMultipleChoiceField(queryset=queryset, widget=forms.CheckboxSelectMultiple())
views.py
def work_search(request):
query = request.GET.get('q', '')
if query:
qset = (
Q(title__icontains=query) |
Q(artist__icontains=query) |
Q(writers__icontains=query)
)
results = Work.objects.filter(qset).distinct()
form = WorkSelectForm(results)
return render_to_response("work_search.html", {"form": form, "query": query })
else:
results = []
return render_to_response("work_search.html", {"query": query })
def add_works(request):
#if request.method == POST:
form = WorkSelectForm(request.POST)
#if form.isvalid():
items = form.fields['works'].queryset
return render_to_response("add_works.html", {"items":items})
work_search.html
{% extends "base.html" %}
{% block content %}
<h1>Search</h1>
<form action="." method="GET">
<label for="q">Search: </label>
<input type="text" name="q" value="{{ query|escape }}">
<input type="submit" value="Search">
</form>
{% if query %}
<h2>Results for "{{ query|escape }}":</h2>
<form action="add_works" method="post">
<ul>
{% if form %}
{{ form.as_ul }}
{% endif %}
</ul>
<input type="submit" value="Add">
</form>
{% endif %}
{% endblock %}
add_works.html
{% extends "base.html" %}
{% block content %}
{% if items %}
{% for item in items %}
{{ item }}
{% endfor %}
{% else %}
<p>Nothing selected</p>
{% endif %}
{% endblock %}
In add_works, you're not constructing your WorkSelectForm the right way. It's expecting as a first parameter the queryset of possible/authorized choices, then the POST data.
Also, you're not accessing the selected works correctly from the form. You have to use is_valid method on the form, then use cleaned_data as described in the doc.
From what I see in your work_search view, there's no restriction on which Work objects you can search then add to the result, so you could do simply:
def add_works(request):
#if request.method == POST:
form = WorkSelectForm(Work.objects.all(), request.POST)
if form.is_valid():
# the items are in form.cleaned_data['works']
items = form.cleaned_data['works']
return render_to_response("add_works.html", {"items":items})
else:
# handle error case here
...