How to create new (unsaved) Django model with associations? - django

I'm building a "preview" function into the CMS for my website which uses the existing front-end template to render a model. This model has an association:
class FeatureWork(models.Model):
name = models.CharField(max_length=100)
...
class FeatureWorkLink(models.Model):
feature_work = models.ForeignKey(FeatureWork)
In the view for the preview, I'm trying to build the model such that when the template calls feature.featureworklink_set.all it returns the associated links. Since neither model has been saved yet, all the standard Django form techniques seem to go out the window.
This is what I have so far, but it blows up when I call the add method on the manager, since the parent hasn't been saved yet:
form = FeatureWorkAdminForm(initial=request.POST)
featured = form.save(commit=False)
for link in request.POST['links'].split(","):
featured.featureworklink_set.add(FeatureWorkLink(image=link))

Why don't you just add:
preview = BooleanField()
to your models, save everything to database and don't look for hacks. This way you can have drafts for free.

You can actually save it in transaction and rollback when template is ready. It's not very efficient, but at least it will work.

featured.featureworklink_set.add(FeatureWorkLink(image=link)) will immediately attempt to create a relationship between a FeatureWork and FeatureWorkLink, which isn't going to happen because that instance of FeatureWork is not present in the database and you cannot satisfy the predicates for building the foreign key relationship.
But the great thing is that Django's Model and ModelForm instances will not validate foreign key relationships until your actually trying to commit the data. So manually constructing your FeaturedWorkLink with an uncommited, non-existing FeatureWork should satisfy any presentation of the data you need to do, much to what you'd expect:
links = []
form = FeatureWorkAdminForm(initial=request.POST)
featured = form.save(commit=False)
for link in request.POST['links'].split(","):
links.add(FeatureWorkLink(image=link, feature_work=featured))
# then somewhere in your templates, from the context
{% for link in links %}
<img src="{{ link.image }}"
title="Image for the featured work: '{{ link.feature_work.name }}'" />
{% endfor %}
So basically, during the course of collecting the data to create a FeatureWork, you'll have to maintain the FeatureWorkLink instances through subsequent requests. This is where you'd use a model form set, but provide an uncommitted FeatureWork for the feature_work property for every model form instance of the set, up until the point where all the data has been collected, where you then provide a commited FeatureWork instance, so that the model form set can satisfy referential integrity and finally be commited to the database.

Related

Django : How can I display only the checked items from a model in a template?

Here is my Django Model:
class InterviewAssessment(models.Model):
topics = models.ManyToManyField(AdvocacyTopic, verbose_name="Topics", blank=True)
They consist into a list of topics out of them one or several items can be selected.
Therefore, when I want to create an object InterviewAssessment, I use this class:
class InterviewForm(models.Model):
class Meta:
model = Interview
fields = ['topics']
widgets = {
'topics': forms.CheckboxSelectMultiple(),
}
My workflow requires that another User, who conducts the interview, updates my form to make an assessment.
Therefore I have created a form dedicated to the assessment, as only some fields are to be updated.
I would like to display only the topics which have been checked. They are not to be updated.
However, if through a widget:
I disable the field with :
self.fields['topics'].widget.attrs.update({'disabled': 'true'})
All the items are displayed in grey, however the field in the database is reset to 0 (which is normal with Django)
I make the field readonly with:
self.fields['topics'].widget.attrs.update({'readonly': 'true'})
The items are selectable and are implemented in the DB, which is worse.
I directly act at the level of the template with:
{{ form.topics }}
Of course, I get all the topics, which I don't want.
Therefore what would be the syntax to exclusively display the checked topics ?
Since you only want to display the selected choices and don't want to update them, don't have topics as part of the form and instead just render them manually by using the instance wrapped by the form (you can style it using CSS, etc. as you wish):
{% for topic in form.instance.topics.all %}
{{ topic }}
{% endfor %}

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 objects.all() method issue

after I saved one item using MyModelClass.save() method of django in one view/page , at another view I use MyModelClass.objects.all() to list all items in MyModelClass but the newly added one always is missing at the new page. i am using django 1.1
i am using mysql
middleware setting
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.locale.LocaleMiddleware',
)
my model:
class Company(models.Model):
name = models.CharField(max_length=500)
description = models.CharField(max_length=500,null=True)
addcompany view
def addcompany(request):
if request.POST:
form = AddCompanyForm(request.POST)
if form.is_valid():
companyname = form.cleaned_data['companyname']
c = Company(name=companyname,description='description')
c.save()
return HttpResponseRedirect('/admins/')
else:
form = AddCompanyForm()
return render_to_response('user/addcompany.html',{'form':form},context_instance=RequestContext(request))
after this page
in another view
i called this form in another view
class CompanyForm(forms.Form):
companies=((0,' '),)
for o in CcicCompany.objects.all():
x=o.id,o.name
companies+=(x,)
company = forms.ChoiceField(choices=companies,label='Company Name')
to list all companies but the recently added one is missing.
The transaction should be successful, since after i do a apache server reboot , i can see the newly added company name
Thanks for any help...
The issue is that you're (once, at import-time) to dynamically building a choices list in your form declaration, but expecting it to be modified each time you use the form. Python doesn't work that way.
See here for docs on choices:
http://docs.djangoproject.com/en/dev/ref/models/fields/#choices
But in particular, this bit:
"[I]f you find yourself hacking choices to be dynamic, you're probably better off using a proper database table with a ForeignKey. choices is meant for static data that doesn't change much, if ever."
Similar applies to forms. Perhaps you want a ModelForm and ModelChoiceField?
As pointed out by other users, the choices are created at the time that you import your module (only once) so they're not going to get updated after the first use.
You should most likely use the django.forms.ModelChoiceField to accomplish this. It takes a queryset and allows you to customize the label as well.
Another way you could do this is to wrap your code in a function, and return a new class each time. That's really not as nice of an approach since the framework provides you with ModelChoiceField, but it's an option since python allows you to return locally defined classes.
Your CcicCompany.objects.all() code is only run once when the Form class is first parsed. So of course any additional objects will not be in this list. You can achieve this in the form's __init__ method instead, so it is run afresh each time a form is generated. You can access the choices via self.fields['field_name'].choices
I'm guessing you're doing something similar to the following:
m = MyModelClass(foo='bar')
m.save()
or
MyModelClass(foo='bar').save()
or
kwargs = {'foo':'bar'}
MyModelClass(**kwargs).save()
Perhaps sharing some code might provide more insight.
Well, I hoped that you would post more code (as I mentioned in my comment), so I'm still guessing...
Are you using TransactionMiddleware and is it possible that the first request does a rollback instead of a commit?
Which database backend are you using? (if it is Sqlite, then consider using PostgreSQL or MySQL, because sqlite does not play nice with regard to concurrent connections)
Please post all relevant code (full class including possible managers in your models.py, as well as the full views), maybe then it's easier for others to help you.
UPDATE:
Your code:
companies=((0,' '),)
for o in Company.objects.all():
x=o.id,o.name
companies+=(x,)
is quite unusual, and it's still not clear what you put into the template. The code is a bit unusual since you are creating a tuple of tuples, but I can't see an error here. The bug must be somewhere else.
Anyway, I'd rather do something like that instead of creating tuples:
view:
companies = Company.objects.all()
return direct_to_template(.... {'companies': companies...} ...)
template:
{% for company in companies %}
<!-- put an empty company at the top of the list for whatever reason -->
{% if forloop.first %}
whatever<br />
{% endif %}
{{ company.name }}<br />
{% endfor %}

Model Formset Issue

I have a model for which the need is to show the form multiple times. I have used it under a modelformset. I seem to have a problem with the id of this model which is also the primary key for the model.
I prepopulate the formset with data which I wish to edit.
But whenever I click on submit it refreshes the page back with an error saying '(Hidden field id) with this None already exists.'
This error comes specifically for the 'id' field which is hidden
<input type="hidden" id="id_form-0-id" value="2972" name="form-0-id"/>
This is the snippet from the template. (I got it from firebug)
What could the issue possibly be since the form is invalid I am not able to save the data.
ProfilesFormSet = modelformset_factory(Profile,exclude = ( <items spearated by commas>), extra=0)
profile_form_set = ProfilesFormSet(queryset = Profile.objects.filter(userprofile=userprofile).order_by('-modified_on'))
This is the code snippet.
If you're using PostgreSQL and any version of Django prior to 1.1beta, and your model does not have a default ordering defined, I think you're probably seeing the bug related to inconsistent ordering of objects returned from the database (see Django Trac tickets 9076, 9758, 10163 among others).
Try setting a default ordering on the model:
class Meta:
ordering = ('some_field',)
See if that fixes it.
I believe this error is caused by one of the following:
The Django form object you are using inside the formset does not include the primary key (id) of the model. However, since you are using modelformset_factory this shouldn't be the case (you also wouldn't be getting that error message).
The HTML form in your template does not include the primary key, even as a hidden field. Make sure you have {{ form.id }} or something like that in your template, inside the {{ for form in formset }} loop.
I can't think of any more reasons at the moment, but I'm sure they are all going to be related to the form POST'ed back from the browser client is missing the id field somehow.