Django - Checkboxes & ManytoMany relationships in TemplateView - django

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

Related

django custom forms with models, also with 'choices' as values, how should I approach this problem?

django forms is giving me a pretty bad headache...
I've been struggling with this for 2 hours now.
I wanted to make a custom form for a model to be able to add objects to it and also be able to move the fields as I want to (instead of using form.as_p (or form|crispy), I wanted to put the entire form in a grid-like table using a custom pattern, not only putting one under another) but whenever I put a choicefield (which is either a choicefield or a foreignkey) their values are empty and also, 3 out of 4 formfields are actually shown, one remains the default.
It contains fields='__all__' with 3 choicefields and 1 floatfield each with a label, nothing more.
To show the forms in html I used
{% for field in form.visible_fields %}
{{ field.label_tag }}
{{ field.errors }}
{{ field }}
{{ field.help_text }}
{% endfor %}
which works well. Am I trying to solve the problem in a wrong way? I'll come back to this topic tomorrow, I'll need some rest now but I don't understand why passing a choices=TheModel # or # choices=TheModel.ojbects.all() breaks the entire thing.
Is there a website or a youtube channel that shows some solutions to those problems?
I lokoed up a bunch of sites and videos but they never access foreign keys as values to forms(dropdowns), never make grouped dropdowns (which I made and is working without custom forms).
Small update, I'm trying with 'labels' and '|as_crispy_field' tags but "exptype" is not changing. Everything else does. and its name is matched too.
https://imgur.com/a/CvP5565
( multiple screenshots attached )
Choices needs to be a tuple like this:
[
('CHOICE_ONE', 'choice_one')
]
So, you could create the choices list like this (Lets assume TheModel has a name field.)
choices = [(i.id, i.name) for i in TheModel.objects.all()]
The second value will be displayed to the user, the first one will be set in the database.
You could use a ModelChoiceField:
class FooMultipleChoiceForm(forms.Form):
foo_select = forms.ModelMultipleChoiceField(queryset=None)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['foo_select'].queryset = ...
https://docs.djangoproject.com/en/4.1/ref/forms/fields/#modelchoicefield

Adding a new ManyToMany relationship in a form?

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.

Django Form with related data from two other tables displayed using a nested (optgroup) <select>

I'm trying to duplicate the functionality of this manually created <select> using a proper Django form.
{% regroup roll_counts by get_type_display as roll_list %}
<select name="film">
<option>Select a film</option>
{% for type in roll_list %}
<optgroup label="{{ type.grouper }}">
{% for film in type.list %}
<option value="{{film.id}}">{{ film.manufacturer }} {{ film.name }} ({{ film.count }})</option>
{% endfor %}
</optgroup>
{% endfor %}
</select>
The queryset for roll_counts looks like this:
roll_counts = Film.objects\
.filter(roll__owner=owner, roll__status='storage')\
.filter(format=camera.format)\
.annotate(count=Count('name'))\
.order_by('type')
I'm trying to recreate this using some sort of Django ModelForm to be able to actually validate data submitted to the form. The ultimate goal is to be able to "load" a camera with a roll of film. Which is to say: associate a Roll (with a foreign key to Film) with a particular Camera and mark said camera with a status of loaded. So the <select> displays data that isn't being updated by the form. What needs to be modified are the Roll and Camera models, not the Film model. But I assumed all that could be handled within the view that receives data from the form.
I'm unsure how to get a ModelForm to display the aforementioned roll_count query (and nested/optgroup <select>) since it doesn't relate to a single field on that Film model.
Any ideas on how to proceed?
Update:
The accepted answer got the nesting part of the puzzle solved, but another part of it was getting the data passed from the view into the form and the QuerySet for the field working.
In the view, you can pass whatever you want to a form instance:
form = LoadCameraForm(owner=owner, format=camera.format)
In the form, I added a custom __init__ (note the owner, format between *args and **kwargs):
def __init__(self, *args, owner, format, **kwargs):
super().__init__(*args, **kwargs)
self.fields['roll_counts'].queryset = Film.objects\
.filter(roll__owner=owner, roll__status='storage')\
.filter(format=format)\
.annotate(count=Count('name'))\
.order_by('type')
(I'm still unsure how to get my annotated count to show up yet.)
Then to put all the pieces together, the field entry in the form looks like this:
roll_counts = GroupedModelChoiceField(\
label='Pick a film to load',\
queryset=None,\
group_by_field='type')
I did that once! See my snippet here :) https://djangosnippets.org/snippets/10573/
There is no built-in form field that can do what you want, so you will have to create your own custom field and a widget for rendering the field to HTML. Take a look at django.forms.fields module and you will see how the form fields are defined. Though for some, it may be a challenge to get it right.
To be honest, i would recommend to search for another option for displaying your form. You could have a select field for type and when selected, you can load the options for film using ajax?
Just my 2 cents :)

How to make Django comments use select_related() on "user" field?

I'm using django comments frameworks. All the comments are posted by authenticated users. Near the comment, I'm showing some user profile info using {{ comment.user.get_profile }}
{# custom comment list templates #}
<dl id="comments">
{% for comment in comment_list %}
<dt id="c{{ comment.id }}">
{{ comment.submit_date }} - {{ comment.user.get_profile.display_name }}
</dt>
<dd>
<p>{{ comment.comment }}</p>
</dd>
{% endfor %}
</dl>
Problem is that django's comment queries does not use select_related() and for 100 comments I get 101 hit on the database.
Is there a way to make django comments framework to select user profile for each comment in one go?
I tested rendering 100 comments for an object with the default {% get_comment_list %} tag and django did 200 comment related queries to list the comments + user + profile because...
Comment.__unicode__ actually calls Comment.user if a user_id exists. +1 query
get_profile +1 query
Ouch!
I went from 203 queries in ~25ms to 3 in ~2ms.
Populate comment_list yourself
I would highly suggest building the comment_list QuerySet yourself using the appropriate select_related() calls. If it's used often, create a utility function called from your other views.
def get_comments_with_user_and_profile(obj):
content_type =ContentType.objects.get_for_model(obj)
return (Comment.objects
.filter(content_type=content_type, object_pk=obj.id)
.select_related('user__profile'))
If you want the entire framework to behave this way... You'll have to monkey patch.
It's not something I would do lightly. There are other ways around this specific problem but you did ask "in one go".
Put this somewhere in your INSTALLED_APPS models.py files. I actually have a monkey_patch app for modifying django.contrib.auth.User.username lengths and such (which is a last resort unlike here).
from django.contrib.comments.models import Comment
from django.contrib.comments.managers import CommentManager
class CommentManager(CommentManager):
def get_query_set(self):
return (super(CommentManager, self)
.get_query_set()
.select_related('user__profile'))
Comment.add_to_class('objects', CommentManager())
Gotchas with profiles and select_related()
Note that your UserProfile class needs a OneToOneField to User with a related_name equal to what you pass to select_related(). In my example it's profile and you need django 1.2+. I recall stumbling on that before.
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile')
# example to use User.objects.select_related('profile')
Assuming that you have a setup like so:
class UserProfile(models.Model):
user = models.ForeignKey(User, related_name='profile')
...
You can use the following select related: Comments.objects.select_related('user__pk','user__profile__pk') and that should do what you want.
You'll have to extend the comments framework. This is fairly straightforward. Basically, create your own comments app. You can look at django-threadedcomments for inspiration (and, actually, in some ways it's already a better implementation to use anyway).
Here's code you can insert into the django-threaded comments app to make sure it always uses the select related (in models.py):
class RelatedCommentManager(CommentManager):
def filter(self, *args, **kwargs):
return super(RelatedCommentManager, self).select_related('user__pk','user__profile__pk').filter(*args, **kwargs)
def exclude(self, *args, **kwargs):
return super(RelatedCommentManager, self).select_related('user__pk','user__profile__pk').exclude(*args, **kwargs)
def all(self)
return super(RelatedCommentManager, self).select_related('user__pk','user__profile__pk').all()
and replace
objects = CommentManager()
with
objects = RelatedCommentManager()
Follow the instructions for integrating threadedcomments into your app.
Then, in the template, I think you'll have to reference .profile instead of .get_profile.
It may be that Django automatically factors this in, so get_profile will not generate another db hit so long as .profile is available.
You can't use select_related() in this example, because User is foreign key of profile, not vice versa.
To avoid using cache (which is probably the best option) you could create proxy model for Comment with foreign key to your profile model. then you could write:
{{ comment.submit_date }} - {{ comment.user.profile.display_name }}

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