I have a form whose purpose is to let a user add, edit, subtract and reorder songs. JavaScript DOM manipulation lets users add, subtract and reorder songs fields. The reordering is via jQuery UI's sortable interaction. The order of songs is crucial.
HTML field name attribute values are duplicated. I'm not using Django to generate the form.
Assuming there are two songs on submission, Firebug shows the form DOM looking something like this (csrf omitted):
<form method="post" action="/save/">
<ul id="sortable_songs" class="ui-sortable">
<li>
<input type="text" name="title" id="song_txt_1">
<textarea id="more_info_txtarea_1" name="more_info"></textarea>
</li>
<li>
<input type="text" name="title" id="song_txt_2">
<textarea id="more_info_txtarea_2" name="more_info"></textarea>
</li>
</ul>
<button type="submit">save</button>
</form>
Example query string:
title=FOO&more_info=FOO+INFO&title=BAR&more_info=BAR+INFO
The model:
class Song(models.Model):
title = models.CharField(max_length=65)
more_info = models.CharField(max_length=255)
#todo: foreignkey to User
Probably not much data is involved, both in each record and with regard to the number of records per user. Hence I'm assuming it makes sense that, for a given user, when the form is submitted I'll delete all of their song instances in the Song table and create a bunch of new ones according to the form. (As opposed to having to edit existing records and having a db field which indicates song order).
It seems like I shouldn't write my own view so I'm trying Django's generic CreateView, but perhaps unsurprisingly, with the above user input only a model instance with "BAR" and "BAR INFO" is created -no "FOO" instance is made.
Is there a simple way around this?
You should use Formsets, in this way you can manage multiple instances of an object in a simple way, however you need to control some extra variables and format your query string. But all is covered in the documentation.
You will need a order field in your model:
class Song(models.Model):
title = models.CharField(max_length=65)
more_info = models.CharField(max_length=255)
#todo: foreignkey to User
order = models.PositiveIntegerField()
class Meta:
order_by = ['order']
# to ensure database integrity
unique_thogeter = [(user, order)]
you may also need to update your create view,
class SongListCreate(CreateView):
model = Song
def get_context_data(self, *args, **kwargs):
context = super(SongListCreate, self).get_context_data(*args, **kwargs)
# add a song formset to the context
# or a bound formset from validation
return context
def form_valid(self, form):
formset = SongFormset(#post data)
objects = formset.save(commit = False)
objects.update(#user instance,
#and other data you might need like the new orders)
objects.save()
return super(SongListCreate, self).form_valid(form)
This is roughly what you may need to do, working with formsets is quite a bit hard while you get used to.
You may also want to use a custom library to manage the order of the songs easily, like moving up or down, something like django-positions.
Regards.
Related
I have the following structure of the project (models):
Company (based on Groups) <-- Products (foreignKey to Company) <-- Reviews (foreignKey to Products).
Inside template i want to give a user an opportunity to filter reviews by products, but when using django_filters it shows corrects queryset of reviews (related only to company's products) but in filter form dropdown options i can see all products of all companies (even those which are not presented on the page), for example for Company X i see on my page only Cactus and Dakimakura reviews, but in filter form i can select Sausage (but i shouldnt, because its product from another company).
For now it's all looks like this:
#View
def reviewsView(request):
context = {}
user = request.user
company = user.company
products_qset = ProductModel.objects.filter(company=company)
reviews_objects = ReviewModel.objects.filter(product__in=products_qset)
filter = ReviewFilter(request.GET, queryset=reviews_objects)
context['filter'] = filter
return render(request, 'companies/reviews.html', context)
#Filter
class ReviewFilter(django_filters.FilterSet):
class Meta:
model = ReviewModel
fields = [
'product',
'marketplace',
'operator',
'state'
]
#Template
<form action="" method="get">
{{ filter.form.as_p }}
<input type="submit" name="press me" id="">
</form>
<div class="review-list">
{% for i in filter %}
{{i.product}}, {{i.marketplace}} etc.
{% endfor %}
I've done quite a lot of research on django_filters docs and this question seems like duplicate, but i can't fully understand what and why is happening to init in this answer and how to correctly rewrike class ReviewFilter so filter instance would do something like this (in View):
filter = ReviewFilter(request.GET, queryset_to_dispplay=MyQueryset, queryset_to_display_filtering_options=MyQueryset). For now its surely happening because ReviewFilter Meta points to the all table (ReviewModel) objects, instead of filtered beforehands.
I also trying to apply pagination on this page (i cut this from question code for "shortness") and after i will be able to implement filtering i would like to merge those two somehow, but if what i want is not possible - please point me to the right direction (for example: 'you can do this by writing your very own filtering system using some js, no need to use django_filters' or 'with angular.js/view.js/somethingelse you can have it all from the box').
I have a django admin page which is for viewing purposes only and rather than displaying data from the model, it is displaying data for a table linked by a foreignkey to an intermediate table which is linked via a foreign key to my model. I want to apply a date range filter on the third table.
class Brand(models.Model):
data ...
class Organisation (models.Model):
data ...
brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
class DailyStat (models.Model):
stat_type = model.CharField(max_lenth=11, choices=STAT_TYPE_CHOICES
date = models.DateField()
organisation = models.ForeignKey(Organisation, on_delete=models.CASCADE)
I then created a change_form.html template in 'templates/admin/brand' which displays the data from DailyStat which I want for the Brand.
But I want to be able to filter this so I created a new form
class BrandAdminForm(forms.ModelForm):
from_date = forms.DateField(widget=admin.widgets.AdminDateWidget())
to_date = forms.DateField(widget=admin.widgets.AdminDateWidget())
class Meta:
model = Brand
fields = ['id','from_date','to_date']
And within the BrandAdmin definition, referenced it
class BrandAdmin(admin.ModelAdmin):
list_display = ['name','get_page_views','cqc_brand_id']
ordering = ['name']
search_fields = ['name']
form = BrandAdminForm
These fields didn't automatically show in the detail page so I added the following within the form tags of {% block content %} of the change_form.html
<table style="width:60%">
<tr>
<td>From: </td>
<td>{{ adminform.form.from_date }}</td>
<td rowspan=2><button type="submit" value="Save and continue editing" class="btn viewsitelink">Filter</button></td>
</tr>
<tr>
<td>To: </td>
<td>{{ adminform.form.to_date }}</td>
</tr>
</table>
So the fields now show in the form (I haven't written the processing to use the fields yet) BUT, I am running django-cms and when I click the filter button, it isn't returning to the pages under the django-cms admin panel rather than returning to the admin view.
If there is a better approach, how can I get to filtering the data I require more effectively OR what am I doing wrong that it isn't returning to the correct view (the form tag shows action="")
Thanks
This is definitely doable, but there are a few things you should consider:
This isn't really what a change-view is meant for. It feels weird from a UX perspective to have (on a page which is essentially one big form) form-elements unrelated to the form you are submitting.
django admin is not meant to be a user-facing production ready environment. If you're trying to make it do things it's not easy to do, it's normally a good sign you should be making your own views here.
The problem with your approach
The modelAdmin.form attribute, is meant to be a form which displays data from an instance of the relevant model, and which saves the returned POST data back to that same instance.
It isn't the form the view actually uses in the end. It is the form django-admin uses to build the form that is finally used though. The admin app does quite a bit of processing along the way, based off attributes that are set on modelAdmin so adding (unrelated) fields to modelAdmin.form won't necessarily correspond to fields on the finally rendered form.
If you want to add in to_date and from_date it should be done in a separate form. (For one thing, you otherwise wouldn't be able to change the dates without submitting your whole form).
Solution
You are far better to add a separate form, and use GET query parameters to update your dates. Something like this:
class DateForm(forms.Form):
from_date = forms.DateField(widget=admin.widgets.AdminDateWidget())
to_date = forms.DateField(widget=admin.widgets.AdminDateWidget())
class BrandAdmin(admin.ModelAdmin):
...
def change_view(self, request, object_id, form_url="", extra_context=None):
date_form = DateForm(request.GET)
if extra_context is None:
extra_context = {}
extra_context['date_form'] = date_form
return super().change_view(
request, object_id, form_url="", extra_context=extra_context
)
Then in your change_form template
{% block content %}
<form method="get">
{{date_form}}
<input type="submit" value="Update dates">
</form>
{{ block.super }}
{% endblock %}
You will now have access to to_date and from_date in your template where you list your via request.GET and you should then be able to filter using them.
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.
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.
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