Adding a new ManyToMany relationship in a form? - django

Django newbie here. I keep encountering the exact same design paradigm, which seems like it should be common for everyone, yet can't find out how it's supposed to be resolved.
Picture the following ManyToMany relationships:
An organization could have many members; each person could be a member of many organizations
An organization could manage many objects. An object could be in use by multiple organizations. The same applies to the relationship between people and objects.
An organization, person, or object could have multiple media elements (photos, videos, etc) of it, and a single media element could be tagged with numerous organizations, people, or objects
Nothing unusual. But how does a site user add a new person, organization, or object? It seems that if someone is filling out an "add an organization" form, in addition to choosing from existing people, objects, media, etc there should be a button for "new member", "new object", "new photo", etc, and this should take you to the forms for creating new members, objects, media, etc. And when they're done, it should go back to the previous page - whose form filled-out form entries should persist, and the newly created entry should be listed in its respective ManyToMany field.
The problem is, I don't know how to do this. I don't know how one would add a button in the middle of a form, and can't seem to find anything to clarify how to do it. I assume it would need to be a submit button, with a different name / id or some other way so that views.py can treat it differently, via flagging an "incomplete" record in the database. And the new form will need to be passed information about what page it needs to go back to when it's submitted.
Am I thinking about this correctly? If so, then I think the only knowledge I lack is how to add a second submit button in a form and how to recognize its usage in views.py.
If I'm not thinking about this correctly, however, please suggest an alternative paradigm that you think makes more sense :) This is my first Django project, so I'm learning as I do it.
ED: I'm thinking maybe instead of using {{ form.as_p }} to display it, I need to iterate over fields and use some logic to add the extra submit button in the middle as html: What's the best way to add custom HTML in the middle of a form with many fields?
Then I'll just need to figure out a way to detect which submit button was used and put some logic behind it to handle partially-submitted forms, redirecting to a form to create the relation, and then redirecting back on submit... I can probably figure this out...

The first thing I would recommend is to define your models. Lay them all out with the attributes you require. That'll be the foundation for everything else you want to accomplish. You can do everything you mentioned with Django... it's just a matter of coding it. As far as I know you would need to create each model instance separately, and then you can refer to already created instances in the create form for the Organization model for example. I would look into the docs for generic views that help you create objects easily. Then you can link to other create forms if you wish. I don't know how you can create multiple instances of different models in one form, and I don't think it would be the best way to do things even if you can. Here's an example of a model, a create form, a create view, and corresponding url:
# models.py
class Organization(models.Model):
name = models.CharField(max_length=100, null=True, blank=True)
# forms.py
class OrganizationForm(forms.ModelForm):
class Meta:
model = Organization
fields = ('name',)
def __init__(self, *args, **kwargs):
super(OrganizationForm, self).__init__(*args, **kwargs)
self.fields['name'].required = True
def clean(self):
cleaned_data = super(OrganizationForm, self).clean()
name = cleaned_data.get('name')
# views.py
class OrganizationCreateView(CreateView): # inherits from CreateView
form_class = OrganizationForm
template_name = 'create_org.html'
success_url = 'success'
def form_valid(self, form): # validate the form and save the model instance
org = form.save(commit=False)
org.save()
return redirect(reverse('redirect_url'))
# urls.py
from Project.apps.app_name import views as app_views
app_name = 'app_name'
urlpatterns = [
url(r'^create_org/$', app_views.OrganizationCreateView.as_view(), name='create_org'), # as_view() is used for class based views
# create_org.html
<form method="post">
{% crsf_token %}
{{ form.as_p }}
<a href="{% url 'app_name:create_person' %}>Create person</a> # You can link to other create views, and just style the link as a button.
<input type="submit" value="Submit">
</form>
Hope that helps.

Related

Django model form with related objects how to save and edit

I am developing an application which have related models like Trip can have multiple loads.
So how can I design the create form to achieve the desired functionality? I know formset can be used to achieve the functionality but I want custom functionality like:
User can create new load while creating trip, the added loads will be show in a html table within the form with edit and delete functionality being on the Trip create page.
I have achieved the functionality using two ways but I am looking for a neater and cleaner approach. What I have done so far was:
I added the Loads using ajax and retrieved the saved load's id and store in a hidden field, when submitting the main Trip's form i create object for Trip and retrieve Loads's object using id and map that Trip id in the Load's object.
I have keep Loads data in the javascript objects and allow user to perform add edit delete Loads without going to the database, once user submit the main Trip form i post Loads's data and create objects and save in the database.
Please let me know if anybody have done this type of work and have a cleaner approach
This sounds like a good use case for django-extra-views.
(install with pip install django-extra-views and add extra_views to your INSTALLED_APPS)
You can create and update models with inlines with simple class based views.
In Views
from extra_views import CreateWithInlinesView, UpdateWithInlinesView, InlineFormSetFactory
from .models import Load, Trip
class LoadInline(InlineFormsetFactory):
model = Load
fields = [<add your field names here>]
class CreateTripView(CreateWithInlinesView):
model = Trip
inlines = [LoadInline]
fields = [<add your trip model fields here>]
template = 'trips/create.html`
template:
<form method="post">
...
{{ form }}
{% for formset in inlines %}
{{ formset }}
{% endfor %}
...
<input type="submit" value="Submit" />
</form>
The update view is very similar. Here are the docs:
https://github.com/AndrewIngram/django-extra-views#createwithinlinesview-or-updatewithinlinesview
The thing that often gets overlooked is commit=False, and the ability to process more than one form or formset in a single view. So you could have a formset for trips, and a form for load, and process the information creating all the objects once all the forms validate.
Here is a restructured view function outline which I use to process multiple forms (haven't edited my app-specific names, don't want to introduce errors):
def receive_uncoated( request): #Function based view
# let's put form instantiation in one place not two, and reverse the usual test. This
# makes for a much nicer layout with actions not sandwiched by "boilerplate"
# note any([ ]) forces invocation of both .is_valid() methods
# so errors in second form get shown even in presence of errors in first
args = [request.POST, ] if request.method == "POST" else []
batchform = CreateUncWaferBatchForm( *args )
po_form = CreateUncWaferPOForm( *args, prefix='po')
if request.method != "POST" or any(
[ not batchform.is_valid(), not po_form.is_valid() ]):
return render(request, 'wafers/receive_uncoated.html', # can get this out of the way at the top
{'batchform': batchform,
'po_form': po_form,
})
#POST, everything is valid, do the work
# create and save some objects based on the validated forms ...
return redirect( 'wafers:ok' )
NB the use of any is vital. It avoids Python short-cut evaluation of a conditional and therefore makes all errors on second and subsequent forms available to the user even if the first form failed validation.
Back to this question: You would replace batch_form with a trip_form, and po_form with a ModelFormset for loads. After your form and formset both validated, you would create all the requested objects using
trip = trip_form.save( commit=False)
load_list = loads_formset.save( commit = False)
# fill in some related object in trip then save trip
foo = Foo.objects.get( ...)
trip.foo = foo
trip.save()
# link loads to trip and save them
for load in load_list:
load.trip = trip
load.save()
You probably want to run this in a transaction so that if something goes wrong (DatabaseError) with saving one of the loads, you don't get a partial trip stored in the database but rather get the whole thing rolled back.

create new parent model in CreateView in django

i am new to django and to this site, so apologies if this has been solved before but i haven't found it
So i have 2 django models
ModelA(Model):
ModelB(Model):
modelA = ForeignKey(ModelA, on_delete=models.CASCADE)
A form for the ModelB
ModelBForm(ModelForm):
class Meta:
model=ModelB
exclude=()
and a view
createModelBView(CreateView):
model = ModelB
form_class = ModelBForm
the template only does
{{form}}
When rendered, there is a dropdown list for the ModelA field so I can choose from existing instances of the ModelA, but what if a new one needs to be created? In the admin there is an option next to edit or create a new ModelA in a popup. Is there an option to do this with CreateView?
Thanks
There is no built-in functionality like that.
However you can build it easily yourself.
You will have to add a link (or a HTML form) in your template which points to the URL corresponding to the view you implemented to create the given model.
Following is a very abstract example.
In your template:
<form>
{{csrf_token}}
{{ form }}
Create model A if you want
<input type="submit" value="Submit">
<\form>
In your urls.py
url(r'^models/createA/$', views.CreateModelAView.as_view(), name="optional")
In your views.py
createModelAView(CreateView):
model = ModelA
form_class = ModelAForm
Then you'll need to create a form called ModelAForm.
On a different note, I'd suggest to start off with functional views if you're new to Django. It is more coding but you get a better feel of what's going on
In the admin there is an option next to edit or create a new ModelA in a popup. Is there an option to do this with CreateView?
No, not built in. That functionality in the admin involves a lot of front-end work involving templates and routing that would have to come from somewhere; since a Form/ModelForm instance can't assume it has access to the admin (which is a contrib module, may not be enabled, and is permission-sensitive), the infrastructure required for that can't be assumed to be available in the general case.
Keep in mind that {{ form }} doesn't even render <form> tags or any kind of submit element. It's intended to be a very, very basic way to render a very, very basic set of fields, while the admin is built specifically to be a (reasonably) powerful, flexible way to put a UI in front of your models.
You could certainly build that functionality yourself, or find a reusable app that does the same thing, but there is no facility distributed with Django to generate it automatically.

Django - Checkboxes & ManytoMany relationships in TemplateView

I have a app where users can register their company and then select a number of settings from a list. Both the company and services are different models.
class Company(models.Model):
name = models.CharField(max_length=100)
(...)
class Service(models.Model):
name = models.CharField(max_length=100)
linked_companies = ManyToManyField(Company, blank=True)
What I want is to have a large list of services, with checkboxes behind their names, so the owner can quickly select the services that he wants to connect to his model. This used to be done through the admin interface, but due popular demand this feature is moved to 'the front'.
The problem is that I do not know how to fit this into the traditional (generic) view/form combinations that we' ve been using so far, since two different models are involved.
I am trying a more custom solution, but have hit a wall and I am wondering if you could help me. I have created a html page that should display both the list of services and a 'save' button.
<form action="." method="POST" class="post-form">{% csrf_token %}
<ul>
{% recursetree services %}
<li>
<label><input type="checkbox" name='service' value={{ node.pk }}><h3>{{ node.name }}</h3></label>
{% if not node.is_leaf_node %}
<ul class="children">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>
<button type="submit" class="save btn btn-default">Add Selected
</button>
</form>
I am using the following ModelForm:
class FacetForm(forms.ModelForm):
class Meta:
model = Services
fields = ['linked_tenants', 'name']
widgets = {
'linked_tenants' : CheckboxSelectMultiple()
}
This HTML page seems to work as intended, showing a long list of services with checkboxes after their names.
However, I have trouble creating a function view. Together with a collegue the following view was created
class FacetList(TenantRootedMixin, TemplateView):
def get_context_data(self, **kwargs):
d = super(ServiceList, self).get_context_data(**kwargs)
d['services'] = Services.objects.all()
d['current_company'] = self.context.company.id
return d
def form_valid(self, *args, **kwargs):
return super(ServiceList, self).form_valid(*args, **kwargs)
This view works in the sense that it shows all of the relevant information (with the checkboxes). If I change the query to filter the services by 'company id'. the view works as desired as well.
The problems I have revolve around the fact that pressing 'save'. crashes the program, throwing the following error.
'super' object has no attribute 'post'
Our program works mostly through generic classbased views and modelforms, so we have relativly limited experience with creating our own custom solutions. By my own estimation the problem seems to be twofold:
The view is probably not configured right to process the 'post' data
It is questionable if the data will be processed to the database afterwards.
Though are 'sollution' is currently flawed, are we looking in the right direction? Are we on the right way to solve our problem?
Regards
I believe you are on the right track. What I would suggest is to not be afraid to move away from generic views and move toward a more custom solution (even if you are inexperienced with it.)
The first routine that comes to my mind would be as follows:
gather all the id's that were checked by the user into a list from request.POST
Update the appropriate object's M2M field to contain these new id's.
Save the fore-mentioned object.
[Edit]
One thing I have trouble with is gathering the ID' s from the request.POST. Could you provide me with an example on how to do this?
Sure, from your HTML file I see you are creating inputs with name=service. That leads me to believe you could do something like:
ids = request.POST.get('service')
but to teach you how to fish rather than giving you a fish, you should try to simply:
print request.POST.items()
This will return and print to the console everything that was posted from your form to your view function. Use this to find out if you are getting a list of id's from the template to the server. If not, you may have to re-evaluate how you are building your form in your template.
Your first point is correct: TemplateView has no "post" method defined and that is why you get the error message when you call super().form_valid. You must either define it yourself or use a CBV which has a post method that you can override (e.g. UpdateView)
And I also believe that your second point is correct.
You would need to use an UpdateView to use the built in functionality (or CreateView).
I had a similar problem to solve (selecting values from many-to-many fields in the front-end) and I ended up with doing it "by hand" because I could not get it to work with CBV. "by-hand" => parse the values from the form, update the database, return HttpResponse
You might want to look at ModelFormSets:
https://docs.djangoproject.com/en/1.11/topics/forms/modelforms/#model-formsets
Hope this helps!
Alex

Django: user interface for changing objects foreign key

Let's say I have a simple set of Django models related by a ForeignKey:
class Author(models.Model):
name = models.CharField('Name', max_length=50)
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField('Title', max_length=50)
rank = models.IntegerField('Rank')
Now in my template I want to create a user interface with two <ol> lists side by side, each list represents the list of Books for a given Author ordered by their rank, and has a form for the Book in the <li> node. Thus the page as a whole consists of an Author formset, each of which consists of an inline Book formset. Hope that is clear...
Now with some fancy javascript/jQuery I allow the user to do two things: 1)she can drag-and-drop a Book form within its <ol> to set its rank, and 2)she can drag-and-drop a Book from one <ol> to the other <ol> with the purpose of changing its Author.
From the Django point of view, there is absolutely no trouble form me to accomplish task 1. The formsets are submitted, the data saved, and Django doesn't know the difference between this method and if the user had simply entered different book ranks in the input text field of course.
However, accomplishing task 2 seems to me a little trickier. If I drag-and-drop a Book form from one Author list to the other, and then use some more fancy javascript to update its input class ids and names(id_1-book-0-title --> id_2-book-0-title for example), along with updating the TOTAL_FORMS and INITIAL_FORMS of the Book formset Management forms to reflect the new number of Book forms in each row, then things don't quite work.
Django treats the dragged form in the list as a new form of course, and creates a genuinely new Book in the database(problematic if you have unique fields since this Book is already in the database). As for the absence of this form in the oldlist, the forms DELETE is not sent and so Django doesn't end up deleting the old object of course. Without any unique fields the result is you get two copies of the Book in the database, one in each Author's list now. With unique fields of some kind of the Book model (e.g. a serial number say) you just hit validation errors.
Does anyone know what the right way to set this up would be?
EDIT: here is the rough view for what I have so far:
def managerView(request):
if request.method == "POST":
author_formset = AuthorFormSet(request.POST, prefix='author')
pairs =[]
for authorform in author_formset:
author=authorform.instance
tempDict={}
tempDict['authorform'] =authorform
tempDict['bookformset'] = BookFormSet(request.POST, prefix=str(author.pk)+'-book', instance=author)
pairs.append(tempDict)
if author_formset.is_valid() and all([pair['bookformset'].is_valid() for pair in pairs]):
author_formset.save()
for pair in pairs:
author=pair['authorform'].instance
#For this author, delete all current books, then repopulate
#from forms in book list that came from request.
old_book_pks = set([book.pk for book in author.books.all()])
new_book_pks = set([bform.instance.pk for bform in pair['bookformset'].forms])
pks_for_trash = old_book_pks - new_book_pks
if len(pair['bookformset'].forms): pair['bookformset'].save()
return HttpResponseRedirect(reverse('admin:index'))
else:
author_formset = AuthorFormSet(prefix='author', queryset=Author.objects.order_by('rank'))
pairs=[]
for f in author_formset:
author=f.instance
#prefix the different book formsets like '1-book' etc
pairs.append({'authorform':f, 'bookformset': BookFormSet(prefix=str(author.pk)+'-book',instance=author)})
myContext= {'authorformset': author_formset, 'pairs':pairs, 'request': request}
return myContext
Now the formsets:
AuthorFormSet = modelformset_factory(Author, extra=0)
BookFormSet = inlineformset_factory(Author, Book, form=BookForm, extra=0, formset=BaseBookFormSet)
Not much going on in the BookForm and BaseBookFormSet except some custom cleaning, so I won't include them just yet, unless anyone thinks they would be useful.
It would be helpful if you also included your form and view code. However, as a general concept this doesn't seem like it should be too hard to implement. Are you using class-based views? It sounds like one way to think about what's going on is that Creation logic is being triggered when you'd rather have Update logic being triggered. CBVs are designed for exactly this sort of thing. In terms of your models, you need to pass a reference to the Book instance's PK up to an Update View (either a Class-based or Functional view) along with the PK of the new Author.
Okay, without actually getting this code running locally, it's hard to know if this will exactly solve your problem, but I think the crux is:
for pair in pairs:
author=pair['authorform'].instance
#For this author, delete all current books, then repopulate
#from forms in book list that came from request.
old_book_pks = set([book.pk for book in author.books.all()])
new_book_pks = set([bform.instance.pk for bform in pair['bookformset'].forms])
pks_for_trash = old_book_pks - new_book_pks
if len(pair['bookformset'].forms): pair['bookformset'].save()
return HttpResponseRedirect(reverse('admin:index'))
Have you tried something like this:
for pk in new_book_pks:
book = Book.objects.get(pk=pk)
book.author = author
book.save()
?
Also, just a note:
if len(pair['bookformset'].forms): pair['bookformset'].save()
Personally, this looks unpythonic to me. The single line conditional probably violates PEP8. Is there a reason why you're using len(pair...)? Are you not able to just do if pair['bookformset'].forms: ?
I realised I was being quite silly. If instead of insisting on using inline formsets for the Books, I just send back a formset of all Books (regardless of Author) and another of all Authors, then every Book has an Author drop down which is trivial to update with javascript upon dragging and dropping it into a new list (this field could made hidden for presentation purposes). Everything then just works upon save as it should.
For the problem of organising the right Books into the right Author <ol> in such a setup, a small template tag filter does the job:
#register.filter(name='forms_for_author')
def forms_for_author(book_formset, authorid):
forms = []
for form in book_formset:
if str(form.instance.tap_id) == str(tapid):
forms.append(form)
return forms
Used in template as
{% for authorform in authorformset %}
<ol class="author">
{% for bookform in bookformset|forms_for_author:authorform.id.value %}
<li>..</li>
{% endfor %}
</ol>
{% endfor %}

Django Problem - trying to access data entered into a form and feed it through a different page

OK, so let me give you an overview first. I have this site and in it there is a form section. When you access that section you can view or start a new project. Each project has 3-5 different forms.
My problem is that I don't want viewers to have to go through all 3-5 pages to see the relevant information they need. Instead I want to give each project a main page where all the essential data entered into the forms is shown as non-editable data. I hope this makes sense.
So I need to find a way to access all that data from the different forms for each project and to feed that data into the new page I'll be calling "Main". Each project will have a separate main page for itself.
I'm pretty much clueless as to how I should do this, so any help at all would be appreciated.
Thanks
You could try this. After that, you could:
Try creating a model for each project. This is done in "models.py" of the application modules created by django-admin
Use views to show that data to people (on your Main page)
If you've already seen all that, then:
First, you should create a view for your main page. So if you have an application my_app, my_app/views.py should be like:
def main_page_view(request, project_name):
# Your code here
pass
Then, to use this, you'd modify urls.py and add in something like:
(r'^projects/(?:<project_name>[a-zA-Z0-9]+)', 'my_app.views.main_page_view'),
Also, you'd need models, which are created in models.py, by subclassing django.models.Model
EDIT: re-reading your question, I guess you need this
Data can be passed from a view to a template through the context.
So say you create a summary view...
def summary(request, *args, **kwargs):
In that view you can query the database using the model api and pass the result of that query into the template for rendering. I'm not sure what your models look like, but say you had a model that had a title and the owner (as a ForeignKey to user)...
class Project(models.Model):
title = models.CharField(max_length=250)
user = models.ForeignKey(User)
Your model will be obviously be different. In your view you could query for all of the models that belong to the current user...
def summary(request, *args, **kwargs):
projects = Project.objects.filter(user=request.user)
Once you've gathered that, you can pass in the query to the template rendering system...
def summary(request, *args, **kwargs):
projects = Project.objects.filter(user=request.user)
render_to_response('project_summary.html', {'projects': projects }, ... )
When you pass the query to the template, you've named it projects. From within the template you can access it by this name...
<body>
<table>
{% for project in projects %}
<tr><td>{{ project.title }}</td></tr>
{% endfor %}
</table>
</body>
(Notice also how you can access a property of the model from within the template as well.)