Django - Can I use one UpdateView to update fields on separate pages? - django

Let's say I have a Student model, with name and age fields, and I have a page with a DetailView class showing these fields. Let's say that rather than having one "update" button that will take me to a form to update all fields of my model at once, I want a separate button for each field that takes me to a separate page with a form to update it.
I know how I could do this with a separate HTML file and separate UpdateView class for each field, but it seems like there should be a cleaner way.
In the first HTML file I have two buttons:
Update name
Update age
In the second I have the form:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
Urls:
urlpatterns = [
path('<int:pk', views.StudentDetailView.as_view(), name="detail"),
path('update_name/<int:pk>', views.StudentUpdateView.as_view(), name="update_name"),
path('update_age/<int:pk>', views.StudentUpdateView.as_view(), name="update_age"),
]
Views:
class StudentUpdateView(UpdateView):
model = models.Student
template_name = 'update_student.html'
I suppose I'm looking for some sort of if statement that I can put in my view, like:
if condition:
fields = ("name",)
elif condition2:
fields = ("age",)
Hopefully this makes sense! Thank you for any help :)

The simplest way to do this is to override the fields in your urls.py file.
urlpatterns = [
path('<int:pk', views.StudentDetailView.as_view(), name="detail"),
path('update_name/<int:pk>', views.StudentUpdateView.as_view(fields=['name']), name="update_name"),
path('update_age/<int:pk>', views.StudentUpdateView.as_view(fields=['age']), name="update_age"),
]
View.as_view accepts keyword arguments, which are used to override the classes attributes for that occurrence.

Related

Understanding Django and Django FormView

I am trying to create a Django web app that accepts text in a form/textbox, processes it and redirects to a webpage showing the processed text . I have written a half-functioning app and find de-bugging quite challenging because I don't understand most of what I've done. I'm hoping you will help me understand a few concepts, Linking to resources, also appreciated.
Consider this simple model:
class ThanksModel(models.Model):
thanks_text = models.CharField(max_length=200)
Is the only way to set the text of thanks_text through the manage.py shell? This feels like a pain if I just have one piece of text that I want to display. If I want to display a webpage that just says 'hi', do I still need to create a model?
Consider the view and template below:
views.py
class TestView(generic.FormView):
template_name = 'vader/test.html'
form_class = TestForm
success_url = '/thanks/'
test.html
<form action = "{% url 'vader:thanks'%}" method="post">
{% csrf_token %}
{{ form }}
<input type = "submit" value = "Submit">
</form>
I need to create another model, view and html template and update urls.py for '/thanks/' in order for the success_url to redirect correctly? (That's what I've done.) Do I need to use reverse() or reverse_lazy() the success_url in this situation?
Models are used when you are dealing with Objects and Data and DataBases that can contain a lot of information.
For Example A Person would be a model. their attributes would be age, name, nationality etc.
models.py
class Person(models.Model):
Name = models.CharField(max_length=50)
age = models.IntegerField()
nationality = models.CharField(max_length=50)
Thi deals with multiple bits of information for one object. (the object being the person)
A Thank you message would not need this? so scrap the model for the thank you message. just have views where you create the view using a templates and setting the view to a url.
views.py
class TestView(generic.FormView):
template_name = 'vader/test.html' # self explantory
form_class = TestForm # grabs the test form object
success_url = reverse_lazy('vader:thanks') # this makes sure you can use the name of the url instead of the path
def ThanksView(request): # its simple so you don't even need a class base view. a function view will do just fine.
return render(request,"thanks.html")
test.html
<form action = "{% url 'vader:thanks'%}" method="post">
{% csrf_token %}
{{ form }}
<input type = "submit" value = "Submit">
</form>
thanks.html
<h1>Thank you for Submitting</h1>
<h2> Come Again </h2>
url.py
from django.urls import path
from djangoapp5 import views
urlpatterns = [
path('', TestView.as_view(), name='test_form'),
path('thanks/', views.ThanksView, name='vader:thanks'),
]
I haven't tested this but hopefully it helps and guide you in the right direction

Wagtail not pulling through custom field panels

I'm overriding the wagtail AbstractFormField panel attribute in the following way:
...
before_input = RichTextField(verbose_name=_('before input'), blank=True)
after_input = RichTextField(verbose_name=_('after input'), blank=True)
panels = [
FieldPanel('label'),
FieldPanel('before_input'),
FieldPanel('after_input'),
FieldPanel('required'),
FieldPanel('field_type', classname="formbuilder-type"),
FieldPanel('choices', classname="formbuilder-choices"),
FieldPanel('default_value', classname="formbuilder-default"),
]
where the other panels are what comes out of the box.
This is working perfectly on the admin side and also saving as rich text into my database
I am pulling this through to my form in my template in the following way:
<form action="{% pageurl page %}" method="POST" class="lm-ls1" id="feedback-form">
{% csrf_token %}
{{ form.question1.help_text }} <!-- Simpler non interable way -->
{{ form.question1.before_input }}
<p>---------------</p>
{% for row in form.fields.values %}
{{row.choices}}
<p>---------------</p>
{{row.help_text}}
<p>---------------</p>
{{row.before_input}}
{% endfor %}
</form>
But I am only getting html output for the form panels excluding the before_input and after_input ones
I am getting through roughly the following:
Overall, how did you feel about the service you received today?
---------------
[('Very satisfied', 'Very satisfied'), ('Satisfied', 'Satisfied'),
('Neither satisfied nor dissatisfied', 'Neither satisfied nor dissatisfied'), ('Dissatisfied', 'Dissatisfied'), ('Very dissatisfied', 'Very dissatisfied')]
---------------
Overall, how did you feel about the service you received today?
---------------
---------------
How can I access the before_input field panel data stored in the _formfield wagtail table?
Bit late but hopefully this still helps you or someone else out there.
How Wagtail Forms Work
Wagtail forms provided to the view context for AbstractFormPage models is a fully instanced Django Form. This means that you will only ever find values in the form that can be given to a Django Form.
This includes fields, which are instances of Django's Fields (eg. CharField) and there is no simple way to add additional attributes to these fields.
You can see how the Form object is built in the Wagtail FormBuilder class definition.
1 - Make a Custom Template Tag
A somewhat simple way to get additional attributes on your FormField (Wagtail's FormField) is using a template tag.
Create a new file in in a folder templatetags in your app, and build a simple_tag that will take the form_page, the field (which will be a Django Field instance) and a string of the attribute name you want to get.
# myapp/templatetags/form_tags.py
from django import template
from django.utils.html import mark_safe
register = template.Library()
#register.simple_tag(name='form_field_attribute')
def form_field_attribute(form_page, field, attribute_name, default=None):
"""Return attribute on FormField where field matches 'field' provided."""
# field is a django Field instance
field_name = field.name
results = [
# if html is stored, need to use mark_safe - be careful though.
mark_safe(getattr(form_field, attribute_name, default))
# get_form_fields() is a built in function on AbstractFormPage
for form_field in form_page.get_form_fields()
# clean_name is property on AbstractFormField used for Django Field name
if form_field.clean_name == field_name]
if results:
return results[0]
return default
2 - Revise your form_page.html Template
In your template, cycle through your form (this is the Django Form instance) and use the template helper to get you the extra attributes you need. Example below, passing in page or self will work the same as they are both the instance of your FormPage.
<form action="{% pageurl page %}" method="POST" role="form">
{% csrf_token %}
{% for field in form %}
<div>{% form_field_attribute page field 'before_input' %}</div>
{{ field }}
<div>{% form_field_attribute page field 'after_input' %}</div>
{% endfor %}
<input type="submit">
</form>

UpdateView with additional fields in Django

I'm building an app where users can submit a ThesisLink, which contains metadata of their MSc or PhD thesis. Before a thesis link is published, a vetting editor must have the possibility to change fields (for example, in the case of a broken link) or outright reject the thesis link. Submitters should be mailed when their thesis link is accepted, accepted with certain changes, or rejected.
I came to the conclusion that I want some sort of UpdateView, where all the fields of the model are already filled out, and ready to be edited by a vetting editor. But I also want fields that are not on the model, like refusal_reason, or editor_comment. And I want to notify users by mail when a change happens.
How to extend the update view to do this? Or should I abandon the UpdateView altogether and build something on top of FormView?
This is what I have so far:
# urls.py
urlpatterns = [
url(r'^vet_thesislink/(?P<pk>[0-9]+)/$', views.VetThesisLink.as_view(), name='vet_thesislink')
]
# views.py
#method_decorator(permission_required(
'scipost.can_vet_thesislink_requests', raise_exception=True), name='dispatch')
class VetThesisLink(UpdateView):
model = ThesisLink
fields = ['type', 'discipline', 'domain', 'subject_area',
'title', 'author', 'supervisor', 'institution',
'defense_date', 'pub_link', 'abstract']
template_name = "theses/vet_thesislink.html"
And in the template:
# templates/theses/vet_thesislink.html
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Update" />
</form>
You will need to create a custom form using ModelForm with additional non-model fields, and use it in UpdateView using the form_class attribute.

How edit data from form django admin

I'm learning Django Framework, and I have a question. To help you understand I will try and explain using the example below:
Suppose that we have some table in db as is:
CREATE TABLE names (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100));
And I have the form in Django Admin as is:
<form>
<textarea name="names"></textarea>
<input type="submit" name="sbt" value="Submit">
</form>
User entered something in the input names in the form and submitted it. Then a script catches this data and splits it into an array (str.split("\n")) and in cycle adding to table names!
And I many quetion:
How i can add form to Django Admin?
How i can catch form data and add this data to somethink table in database?
Thanks.
First of all you must create a django model.
Put this code in models.py.
class Names(models.Model):
name = models.CharField(max_length = 100)
Then you must create the admin model.
Put this code in admin.py.
class NamesAdmin(admin.ModelAdmin):
list_display = ['name']
# whatever you want in your admin panel like filter, search and ...
admin.site.register(Names, NamesAdmin)
I think it meet your request. And for split the names you can override save model method and split the names in there. But if you want to have an extra form, you can easily create a django model form.
Put the code somewhere like admin.py, views.py or forms.py
class NamesForm(forms.ModelForm)
class Meta:
model = Names
That's your model and form. So, if your want to add the form to django admin panel you must create a view for it in django admin. For do this create a view as common.
Put the code in your admin.py or views.py.
def spliter(req):
if req.method == 'POST':
form = NamesForm(req.POST)
if form.is_valid():
for name in form.cleaned_data['names'].split(' '):
Names(name = name).save()
return HttpResponseRedirect('') # wherever you want to redirect
return render(req, 'names.html', {'form': form})
return render(req, 'names.html', {'form': NamesForm()})
Be aware you must create the names.html and put the below code in you html page.
{% extends 'admin/base_site.html' %}
{% block content %}
<!-- /admin/names/spliter/ is your url in admin panel (you can change it whatever you want) -->
<form action="/admin/names/spliter/" method="post" >{% csrf_token %}
{{ form }}
<input type="submit" value="'Send'" >
</form>
{% endblock %}
This is your view and your can use it everywhere. But if you want only the admin have permission to see this page you must add this method too your NamesAdmin class.
def get_urls(self):
return patterns(
'',
(r'^spliter/$', self.admin_site.admin_view(spliter)) # spliter is your view
) + super(NamesAdmin, self).get_urls()
That's It. I hope this can help you.

Django and fieldsets on ModelForm

I know you can specify fieldsets in django for Admin helpers. However, I cannot find anything useful for ModelForms. Just some patches which I cannot use. Am I missing something? Is there a way I could achieve something like fieldsets without manually writing out each field on my template in the appropriate tag.
I would ideally like to iterate through a set of BoundFields. However, doing something like this at the end of my ModelForm:
fieldsets = []
fieldsets.append(('Personal Information',
[username,password,password2,first_name,last_name,email]),) # add a 2 element tuple of string and list of fields
fieldsets.append(('Terms & Conditions',
[acceptterms,acceptprivacy]),) # add a 2 element tuple of string and list of fields
fails as the items contained in my data structure are the raw fields, not the BoundFields. t looks like BoundFields are generated on the fly... this makes me sad. Could I create my own subclass of forms.Form which contains a concept of fieldsets (even a rough one that is not backward compatible... this is just for my own project) and if so, can you give any pointer? I do not want to mess with the django code.
I think this snippet does exactly what you want. It gives you a Form subclass that allows you to declaratively subdivide your form into fieldsets and iterate through them in your template.
Update: that snippet has since become part of django-form-utils
Fieldsets in modelforms are still in "design" stage. There's a ticket in Django trac with low activity.
It's something I've been interested in researching myself in the near future, but since I haven't done it yet the best I can offer are these snippets:
Form splitting/Fieldset templatetag
Sectioned Form
Forms splitted in fieldsets
Edit: I just noticed this question again and I realize it needs an edit to point out Carl's project django-form-utils which contains a BetterForm class which can contain fieldsets. If you like this project give him a +1 for his answer below :)
One thing you can do is break your logical fieldsets into separate model form classes.
class PersonalInfoForm (forms.ModelForm):
class Meta:
model=MyModel
fields=('field1', 'field2', ...)
class TermsForm (forms.ModelForm):
class Meta:
model=MyModel
fields=('fieldX', 'fieldY', ...)
Pass them to your template in different variables and break up the formsets:
<form ...>
<fieldset><legend>Personal Information</legend>
{{ personal_info_form }}
</fieldset>
<fieldset><legend>Terms and Conditions</legend>
{{ terms_form }}
</fieldset>
</form>
In that sense each of your form classes is just a fragment of the actual HTML form.
It introduces a touch of complexity when you call save on the form. You'll probably want to pass commit=False and then merge the resultant objects. Or just avoid using ModelForm.save altogether and populate your model object by hand with 'cleaned_data'
Daniel Greenfelds django-uni-form solves this with a the Layout helper class. I'm trying it out right now and it looks pretty clean to me.
Uniform helpers can use layout objects. A layout can consist of fieldsets, rows, columns, HTML and fields.
I originally picked Django-uni-form because it complies with section 508.
You can use this package: https://pypi.org/project/django-forms-fieldset/
pip install django-forms-fieldset
Add forms_fieldset to your INSTALLED_APPS setting like this:
INSTALLED_APPS = [
...
'forms_fieldset',
]
Add fieldsets in your form
from django.forms import ModelForm
from .models import Student
class StudentForm(ModelForm):
fieldsets = [
("Student Information", {'fields': [
('first_name', 'last_name'),
('email', 'adress'),
]}),
("Parent Information", {'fields': [
'mother_name',
'father_name',
]}),
]
class Meta:
model = Student
fields = '__all__'
In your views
def home(request):
form = StudentForm()
if request.method == 'POST':
form = Form(request.POST, request.FILES)
#save...
context = {
'form': form,
}
return render(request, 'home.html', context)
in your template
{% load forms_fieldset static %}
<link rel="stylesheet" type="text/css" href="{% static 'forms_fieldset/css/main.css' %}">
<form>
{{ form|fieldset:'#42945c' }}
</form>
This was the code that I developed in order to understand custom tags (with links). I applied it to create a fieldset.
Disclaimer: I encourage the use of any of the above answers, this was just for the sake of learning.
templatetags/myextras.py:
from django import template
from django.template import Context
register = template.Library()
class FieldsetNode(template.Node):
""" Fieldset renderer for 'fieldset' tag """
def __init__(self, nodelist, fieldset_name):
""" Initialize renderer class
https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#writing-the-renderer
:param nodelist: a list of the template nodes inside a block of 'fieldset'
:param fieldset_name: the name of the fieldset
:return: None
"""
self.nodelist = nodelist
self.fieldset_name = fieldset_name
def render(self, context):
""" Render the inside of a fieldset block based on template file
https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#auto-escaping-considerations
:param context: the previous template context
:return: HTML string
"""
t = context.template.engine.get_template('myapp/fieldset.html')
return t.render(Context({
'var': self.nodelist.render(context),
'name': self.fieldset_name,
}, autoescape=context.autoescape))
#register.tag
def fieldset(parser, token):
""" Compilation function for fieldset block tag
Render a form fieldset
https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#writing-the-compilation-function
https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#parsing-until-another-block-tag
:param parser: template parser
:param token: tag name and variables
:return: HTML string
"""
try:
tag_name, fieldset_name = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError("%r tag requires a single argument" % token.contents.split()[0])
if not (fieldset_name[0] == fieldset_name[-1] and fieldset_name[0] in ('"', "'")):
raise template.TemplateSyntaxError("%r tag's argument should be in quotes" % tag_name)
nodelist = parser.parse(('endfieldset',))
parser.delete_first_token()
return FieldsetNode(nodelist, fieldset_name[1:-1])
templates/myapp/fieldset.html:
<div class="fieldset panel panel-default">
<div class="panel-heading">{{ name }}</div>
<div class="panel-body">{{ var }}</div>
</div>
templates/myapp/myform.html:
<form action="{% url 'myapp:myurl' %}" method="post">
{% csrf_token %}
{% fieldset 'General' %}
{{form.myfield1 }}
{% endfieldset %}
{# my submit button #}
</form>