Django: Only update fields that have been changed in UpdateView - django

I'm using an UpdateView to update a series of fields. However, I only want fields that have been modified to be saved to the database. If a value was not provided for a field during update process, I want the previous value to be used as the default. If a new value was provided for a field then only that field should be updated. How do I go about accomplishing this?
#views.py
class AccountUpdate(UpdateView):
""" Updates an account; unchanged fields will not be updated."""
context_object_name = 'account'
form_class = UpdateForm
template_name = 'accounts/update_account.html'
success_url = '/accounts/home'
def get_object(self, queryset=None):
return self.request.user
def form_valid(self, form):
clean = form.cleaned_data
#encrypt plain password
form.instance.password = hash_password(clean['password'])
context = {}
self.object = context.update(clean, force_update=False)
return super(AccountUpdate, self).form_valid(form)
#templates
{% extends 'base.html' %}
<title>{% block title %}Update Account{% endblock %}</title>
{% block content %}
{{ account.non_field_errors }}
<div class="errors" style="list-style-type: none;">
{% if account.first_name.errors %}{{ account.first_name.errors }}{% endif %}
{% if account.last_name.errors %}{{ account.last_name.errors }}{% endif %}
{% if account.email.errors %}{{ account.email.errors }}{% endif %}
{% if account.password.errors %}{{ account.password.errors }}{% endif %}
{% if account.username.errors %}{{ account.username.errors }}{% endif %}
</div>
<form action="" name="edit" method="post">
{% csrf_token %}
<ul>
<li>Fullname</li>
<li> {{ form.first_name }}{{ form.last_name }}</li>
<li>Email</li>
<li>{{ form.email }}</li>
<li>Password</li>
<li>{{ form.password }}</li>
<li>Username</li>
<li>{{ form.username }}</li>
</ul>
<ul>
<li><input type="submit" value="update account"></li>
</ul>
</form>
<ul>
<li class="nav">cancel changes</li>
</ul>
{% endblock %}
All the fields are also declared as required in models.py. Currently the form only works if i provide a value for each field.

i'm using a custom hash during update process to encrypt passwords. When i visit the edit page and hit update button, the old password in its current encrypted form gets re-encrypted hence losing the old password
I would handle that by not including password itself in the form. Instead, I would add a new field (something like new_password) to allow entering a new password. Then, in your is_valid method, set the password to the hashed value of that field if there's content.
You should also use the sensitive value filtering tools to prevent user passwords from showing up in emailed error reports.
class UpdateForm(forms.ModelForm):
class Meta:
model = user
fields = ('first_name', 'last_name', 'email', 'username')
new_password = forms.CharField(required=False, widget=forms.widgets.PasswordInput)
And then in your view:
#sensitive_variables('new_password')
#sensitive_post_parameters('new_password')
def form_valid(self, form):
clean = form.cleaned_data
new_password = clean.get('new_password')
if new_password:
#encrypt plain password
form.instance.password = hash_password(new_password)
return super(AccountUpdate, self).form_valid(form)

The easiest way would be to pre-populate the fields with what's already there.
Do this on the template with {{ account.name }} or whatever.

Related

Show django form in a designed page

How are you?
I m totally new in Django.I designed a page and I wanted to show a django form(edit or create) in a well designed HTML page. but i do not know how.
This is my owner method:
class OwnerUpdateView(LoginRequiredMixin, UpdateView):
"""
queryset to the requesting user.
"""
def get_queryset(self):
print('update get_queryset called')
""" Limit a User to only modifying their own data. """
qs = super(OwnerUpdateView, self).get_queryset()
return qs.filter(user=self.request.user)
class OwnerCreateView(LoginRequiredMixin, CreateView):
"""
Sub-class of the CreateView to automatically pass the Request to the Form
and add the owner to the saved object.
"""
# Saves the form instance, sets the current object for the view, and redirects to get_success_url().
def form_valid(self, form):
print('form_valid called')
object = form.save(commit=False)
object.user = self.request.user
object.save()
return super(OwnerCreateView, self).form_valid(form)
This is my views.py
class TaskUpdateView(OwnerUpdateView):
model = Task
fields = ["title", "text", "endDate"]
class TaskCreateView(OwnerCreateView):
model = Task
fields = ["title","text","status","endDate"]
This is my urls.py:
app_name='task'
urlpatterns = [
path('', views.TaskListView.as_view(), name='all'),
path('task/<int:pk>/', views.TaskDetailView.as_view(), name='detail'),
path('task/create', views.TaskCreateView.as_view(success_url=reverse_lazy('task:all')), name='task_create'),
path('task/update/<int:pk>', views.TaskUpdateView.as_view(success_url=reverse_lazy('task:all')),
name='task_update'),
path('task/delete/<int:pk>', views.TaskDeleteView.as_view(success_url=reverse_lazy('task:all')),
name='task_delete'),
path("accounts/login/", views.login, name='login'),
path("accounts/logout/", views.logout, name='logout'),
]
And this is the models.py:
class Task(models.Model):
title=models.CharField(max_length=250)
text=models.TextField()
user=models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=False)
status=models.ForeignKey('Status',on_delete=models.SET_NULL,null=True)
startDate=models.DateTimeField(auto_now_add=True)
endDate=models.DateField(null=True)
def __str__(self):
return self.title
class Status(models.Model):
name=models.CharField(max_length=250)
def __str__(self):
return self.name
And this is where these both function work:
{%extends 'base.html'%}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<table>{{ form.as_table }}</table>
<input type="submit" value="Submit">
{# <input type="submit" onclick="window.location='{% url 'project:all' %}' ; return false;" value="Cancel">#}
</form>
{% endblock %}
How can i separate each element of this form and put it in a better designed page?
Thanks
There are two ways:
Option 1:
Loop over the form fields and render them individually:
{% for field in form %}
<div class="form-group">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<span class="form-text">{{ field.help_text|safe }}</span>
{% endif %}
</div>
{% endfor %}
See docs for more.
Option 2:
You can manually create form inputs and give them the correct field name attribute. This gives you more control but also requires more work:
<div class="form-group"
<input
type="text"
name="title"
value="{{ form.title.value }}"
class="form-control {% if form.title.errors %}is-invalid{% endif %}"
>
{% if form.title.help_text%}
<span class="form-text">{{ form.title.help_text|safe }}</span>
{% endif %}
<div class="invalid-feedback">{{ form.title.errors }}</div>
</div>
<!-- now do the same for other fields -->

Django inlineformset will not submit

My application has two models, Recipe and RecipeIngredient, related by foreign key. Each Recipe may have many different RecipeIngredients (and different number of associated ingredients for each recipe). So using a dynamic formset to assign ingredients to a recipe seems to be the best approach.
I have a simple form that has a user enter a quantity and select a unit and ingredient from a dropdown. As a standalone form it works fine.
Unfortunately, I cannot get any formset incorporating it to submit.
I have searched many of the different formset Q&As here on StackOverflow but cannot see where the problem is. When I click 'submit', the form blinks and refreshes in place, with the entered values still in the form. No objects are created. No errors report to the console nor to the browser. The related code:
The models:
class RecipeBase(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
name = models.CharField(max_length=128,null=False)
creation_date = models.DateField("Record creation date",auto_now_add=True)
creation_user = models.CharField("Record creation user",max_length=150)
lastupdated_date = models.DateField(auto_now=True)
lastupdated_user = models.CharField(max_length=150)
category = models.ForeignKey(RecipeCategory,on_delete=models.CASCADE,related_name='recipes')
subcategory = models.ForeignKey(RecipeSubcategory,on_delete=models.CASCADE,related_name='recipes')
def __str__(self):
return str(self.name)
class RecipeIngredient(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
quantity = models.DecimalField(max_digits=8,decimal_places=3,null=False)
referenced_unit = models.ForeignKey(UnitDetail,on_delete=models.CASCADE)
referenced_ingredient = models.ForeignKey(Ingredient,on_delete=models.CASCADE)
parent_recipe = models.ForeignKey(RecipeBase,on_delete=models.CASCADE,related_name='ingredients')
def __str__(self):
return str(self.id)[:8]
The call to enter ingredients:
<a class='btn btn-success' href="{% url 'recipes:addingredient' pk=recipe_details.pk %}">Add Ingredients</a>
Calls the view:
class AddIngredientView(CreateView):
form_class = AddIngredientForm
model = RecipeIngredient
template_name = 'addingredientmultiple2.html'
success_url = "recipes:listrecipes"
def get_context_data(self, **kwargs):
parent_recipe_id = RecipeBase.objects.get(id=self.kwargs['pk'])
data = super(AddIngredientView, self).get_context_data(**kwargs)
if self.request.POST:
data['ingredients'] = AddIngredientFormset(self.request.POST)
data['parent_recipe_id'] = parent_recipe_id
else:
data['ingredients'] = AddIngredientFormset()
data['fixture'] = parent_recipe_id
return data
def form_valid(self, form, **kwargs):
user = self.request.user
fixture = RecipeBase.objects.get(id=self.kwargs['pk'])
context = self.get_context_data()
formset = AddIngredientFormset(self.request.POST)
if formset.is_valid():
ingredients = formset.save()
for recipeingredient in ingredients:
#recipeingredient.creation_user = str(request.user)
#recipeingredient.lastupdated_user = str(request.user)
recipeingredient.parent_recipe_id = parent_recipe_id
#recipeingredient.user = user
recipeingredient.save()
return super(AddIngredientView, self).form_valid(form)
Using form and formset:
class AddIngredientForm(forms.ModelForm):
quantity = forms.DecimalField(widget=forms.NumberInput(attrs={'data-placeholder': 0.00,'size': '8','label_tag': ''}))
referenced_unit = forms.ModelChoiceField(queryset=UnitDetail.objects.all())
referenced_ingredient = forms.ModelChoiceField(queryset=Ingredient.objects.all())
class Meta():
model = RecipeIngredient
fields = ('quantity','referenced_unit','referenced_ingredient',)
AddIngredientFormset = inlineformset_factory(RecipeBase,RecipeIngredient,form=AddIngredientForm,extra=1,can_delete=True)
And template:
{{ ingredients.media.css }}
<div class="container">
<h2>Add ingredients to {{ referring_recipe_name }}</h2>
<h4>{{ referring_recipe_id }}</h4>
</div>
<div class="col-md-4">
<form class="form-horizontal" enctype="multipart/form-data" method="post">{% csrf_token %}
<table class="table">
{{ ingredients.management_form }}
{% for form in ingredients.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="formset_row">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input class='btn btn-success' type="submit" value="Save"/> back to the list
</form>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="{% static 'formset/jquery.formset.js' %}"></script>
<script type="text/javascript">
$('.formset_row').formset({
addText: 'ADD INGREDIENT',
deleteText: 'REMOVE',
prefix: 'ingredients'
});
</script>
{{ ingredients.media.js }}

Django: ModelFormSet saving first entry only

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 %}

django display ManyToManyField as a form field

I am trying to display a ManyToManyField in my template:
class GvtCompo(models.Model):
startDate=models.DateField(max_length=10, blank=False, null=False)
endDate=models.DateField(max_length=10, blank=False, null=False)
gvtCompo= models.CharField(max_length=1000, blank=False, null=False)
class Act(models.Model):
gvtCompo= models.ManyToManyField(GvtCompo)
In my view, I can display the object, no problem:
for gvtCompo in act.gvtCompo.all():
print gvtCompo.gvtCompo
In my template, I have a list of "GvtCompo Object" with this code (not nice):
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field }}
</div>
{% endfor %}
I have tried to make it nicer, but the following code just not work (nothing appears):
{% for field in form %}
{% if field.name == "gvtCompo" %}
{% for gvtCompo in field.gvtCompo.all %}
{{ gvtCompo.gvtCompo }}
{% endfor %}
{% endif %}
{% endfor %}
What's wrong?
*Edit: *
If I don't use the form but an instance of the model (act) passed to render_to_response it displays the ManyToManyField values
{% for gvtCompo in field.gvtCompo.all %}
changed to
{% for gvtCompo in act.gvtCompo.all %}
However there is not form field anymore, so it can't be modified, validated and saved!
You are skipping a step. You first need to create a form.
In forms.py you can create a ModelForm. A ModelForm is a form based on your model:
from django.forms import ModelForm
from myapp.models import Act
class ActForm(ModelForm):
class Meta:
model = Act
Then your view:
from myapp.models import Act
from myapp.forms import ActForm
def add_view(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
form.save()
else:
form = ActForm() # Creating a empty form.
return render_to_response("template.html", {
"form": form,
})
def edit_view(request):
obj = Act.objects.get(pk=1)
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
form.save()
else:
form = ActForm(instance=obj) # Creating form pre-filled with obj.
return render_to_response("template.html", {
"form": form,
})
If you want to implement this situation more than once. DRY: https://docs.djangoproject.com/en/1.5/topics/class-based-views/intro/#handling-forms-with-class-based-views
In your template.html:
{{ form }}
Disclaimer: This code is not tested.
https://docs.djangoproject.com/en/1.5/ref/forms/
https://docs.djangoproject.com/en/1.5/topics/forms/modelforms/
Update:
You can pass multiple forms to one <form>...</form> in your template. So create two forms. One Act form (see above) and one GvtCompo formset. The formset contains all GvtCompo's that have a relation to Act.
from django.forms.models import modelformset_factory
act = Act.objects.get(pk=1) #The same act as you used to create the form above.
GvtFormSet = modelformset_factory(GvtCompo)
formset = GvtFormSet(queryset=act.gvtCompo.all())
Template can be:
<form ...>
{% for field in form %}
{% if field.name == "gvtCompo" %}
{{ formset }}
{% else %}
{{ field }}
{% endif %}
{% endfor %}
</form>
Note: If your form and formset have colliding field names use prefix="some_prefix":
Django - Working with multiple forms
When looping over the form, it should be:
{% for field in form %}
{% if field.name == "gvtCompo" %}
{% for gvtCompo in form.instance.gvtCompo.all %}
{{ gvtCompo.gvtCompo }}
{% endfor %}
{% endif %}
{% endfor %}
field itself has no related field.gvtCompo.

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
...