I have a ManyToMany relation between 2 of my models in the same app. This looks like following:
class Event(models.Model):
eventID = models.CharField(deafult = random_eventID)
signal = models.ManyToManyField(Signal)
....
....
Now, in my Form (using ModelForm) my eventID field is already populated with the eventID every time i refresh the page (because it gets a new random_eventID every time i refresh the page).
This way when, in my forms i select to add a new signal (I want to be able to add signals when i create an event)...it goes to a different view. I save the event and when i return back to the Event page, the eventID is changed again. I want to have all the data which the user has already filled/selected in the form to be present when it returns back to the Event page after adding lots of different stuff.
Solutions i thought of :
1 - I cannot make changes to my model so as to include another column and save the Event before going to another page & later retrieve it back.
2 - Using sessions i save all the data already present in all the fields an later retrieve it back..( This way HTTP is no more stateless, but it serves my purpose.)
3 - Will Ajax help in doing any update (which i don't understand it will)
I tried it using session and came cross this silly error, which i am not able to resolve.
views.py
def create(request):
if request.POST:
form = EventForm(request.POST)
form .save()
del request.session['event_id']
return HttpResponseRedirect('/Event')
else:
event_session = request.session.get('event_id')
if event_session is not None:
form = EiEventForm(initial={'eventID' : event_session}
else:
form = EventForm()
request.session['event_id'] = form('eventID').value()
args = {}
args.update(csrf(request))
args['form'] = form
return render_to_response('event.html', args)
With the above, after debugging i do not get the current value in the eventID field..I tried some other ways as well but with no success.
The request.GET.get('eventID') returns None..How can i get the values from my field ?
Also, is there a better way to accomplish the desired result except sessions.
Any help would be great help!
Related
i am building an application that has a model with three fields Company,Name, position. in the same model i want to have company name as one field while the user can add name and positions for multiple candidates. the reason am trying to do that is because i didnt find any proper way to set automatically select the foreign key based on the company name entered since foreign key is a drop down list and couldnt figure out the way to make foreign key field equal to company name entered.
appreciate help and suggestions if any for the approach i have in mind.
You need two forms (or more usefully one form and one formset). Use form prefixes to make them distinguishable. Pass both to the template, say as selectform and candidate_formset and in the template, use
{{selectform}}
{{candidate_formset}}
The first is a company-select form. It might, for example, be
class CompanySelectForm(forms.ModelForm):
class Meta:
model = Candidate
fields = ['company']
The second is a form, or probably a formset, for entering one, or (via a formset) as many candidates as there are to be entered. It will look like
class CandidateForm(forms.ModelForm):
class Meta:
model = Candidate
fields = ['name','position']
Now, you use commit=False (docs) to create objects but not save them. First, process CandidateSelectForm, which will give you a Candidate object with a valid company instance, but not save it. Then process the formset of CandidateForm, again with commit=False, which will give you a list of candidate instances with no company, again unsaved. Finally for each candidate in this list, set the company field of every candidate to the one on the object retrieved by CandidateSelectForm and save it.
It will probably be easier to write a plain view function, than messing around with method overrides trying to get the class-based views to process two forms this way.
Edit - added on request.
The view could be modelled on this one in the Django doc. I've made the obvious changes in line with the earlier part of the answer, but it's probably full of errors and I'm not going to debug it further here
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create form instances and populate with data from the request:
cs_form = CompanySelectForm(request.POST, prefix="cs")
cand_form = CandidateSelectForm( request.POST, prefix="cand")
# check whether it's valid:
if cs_form.is_valid() and cand_form.is_valid():
selector = cs_form.save(commit=False)
candidate = cand_form(commit=False)
candidate.company = selector.company
candidate.save()
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
cs_form = CompanySelectForm( prefix='cs')
cand_form = CandidateSelectForm( prefix='cand')
return render(request, 'name.html', {
"select_form": cs_form,
"candidate_form": cand_form,
})
Once you have got this working for a single candidate, you can progress to turning candidate_form into a formset, documented here which will let you enter any number of candidates to be attached to the one selected company.
I'm using django-filter which is working great but I am having a problem filtering my drop down list of choices (which is based on a model) by the current user. It's a fairly basic and common scenario where you have a child table which has a many to one relationship to a parent table. I want to filter the table of child records by selecting a parent. This is all fairly easy, standard stuff. The fly in ointment is when the parent records are created by different users and you only want to show the parent records in the drop down list that belongs to the current user.
Here is my code from filters.py
import django_filters
from django import forms
from .models import Project, Task
from django_currentuser.middleware import get_current_user, get_current_authenticated_user
class MasterListFilter(django_filters.FilterSet):
project = django_filters.ModelChoiceFilter(
label='Projects',
name='project_fkey',
queryset=Project.objects.filter(deleted__isnull=True, user_fkey=3).distinct('code')
)
class Meta:
model = Task
fields = ['project']
#property
def qs(self):
parent = super(MasterListFilter, self).qs
user = get_current_user()
return parent.filter(master=True, deleted__isnull=True, user_fkey=user.id)
This bit works fine:
#property
def qs(self):
parent = super(MasterListFilter, self).qs
user = get_current_user()
return parent.filter(master=True, deleted__isnull=True, user_fkey=user.id)
This filters my main list so that only records that have a master flag set, have not been deleted and belong to the current user are shown. This is exactly what I want.
This following bit also works and gives me the filtered drop down list that I am looking for because I have hardcoded 3 as the user.id
queryset=Project.objects.filter(deleted__isnull=True, user_fkey=3).distinct('code'),
Obviously I don't want to have a hardcoded id. I need to get the value of the current user. Following the same logic used for filtering the main table I end up with this.
class MasterListFilter(django_filters.FilterSet):
**user = get_current_user()**
project = django_filters.ModelChoiceFilter(
label='Projects',
name='project_fkey',
queryset=Project.objects.filter(deleted__isnull=True, user_fkey=**user.id**).distinct('code')
)
However this is unreliable as sometimes it shows the correct list and sometimes it doesn't. For example if I login and it's not showing the list (ie it shows just '---------') and then I restart my apache2 service, it starts to work again, then at some point it drops out again. Clearly this is not a long term solution.
So how do I reliably get the current user into my filter.py so that I can use it to filter my drop down filter list.
Thanks in advance and happy coding.
EDIT:
So following Wiesion's suggestion I changed my code as suggested but I still get a None Type Error saying that user has no attribute ID. BAsically it seems I'm not getting the current user. So going back to the docs and trying to merge their suggestion with Wiesion (whose explanation makes total sense - Thanks Wiesion) I came up with the following:
def Projects(request):
if request is None:
return Project.objects.none()
return lambda req: Project.objects.filter(deleted__isnull=True, user_fkey=req.user.id)
class MasterListFilter(django_filters.FilterSet):
project = django_filters.ModelChoiceFilter(
label='Projects',
name='project_fkey',
queryset=Projects
)
class Meta:
model = Task
fields = ['project']
This kind of works in theory but gives me nothing in the drop down list because
if request is None:
is returning True and therefore giving me an empty list.
So...can anyone see where I'm going wrong which is preventing me from accessing the request? Clearly the second portion of code is working based on qs that is passed from my view so maybe I need to pass in something else too? My view.py code is below:
def masterlist(request, page='0'):
#Check to see if we have clicked a button inside the form
if request.method == 'POST':
return redirect ('tasks:tasklist')
else:
# Pre-filtering of user and Master = True etc is done in the MasterListFilter in filters.py
# Then we compile the list for Filtering by.
f = MasterListFilter(request.GET, queryset=Task.objects.all())
# Then we apply the complete list to the table, configure it and then render it.
mastertable = MasterTable(f.qs)
if int(page) > 0:
RequestConfig(request, paginate={'page': page, 'per_page': 10}).configure(mastertable)
else:
RequestConfig(request, paginate={'page': 1, 'per_page': 10}).configure(mastertable)
return render (request,'tasks/masterlist.html',{'mastertable': mastertable, 'filter': f})
Thanks.
From the docs
The queryset argument also supports callable behavior. If a callable
is passed, it will be invoked with Filterset.request as its only
argument. This allows you to easily filter by properties on the
request object without having to override the FilterSet.__init__.
This is not tested at all, but i think something along these lines this is what you need:
class MasterListFilter(django_filters.FilterSet):
project = django_filters.ModelChoiceFilter(
label='Projects',
name='project_fkey',
queryset=lambda req: Project.objects.filter(
deleted__isnull=True, user_fkey=req.user.id).distinct('code'),
)
class Meta:
model = Task
fields = ['project']
Also if it's depending from webserver restarts - did you check caching issues? (In case, django-debug-toolbar gives great insights about that)
EDIT
The unpredictable behaviour most probably happens because you are retrieving the user within the class MasterListFilter definition, so get_current_user() is executed at class loading time, not during an actual request and all subsequent calls to qs will retrieve that query. Generally everything request-related should never be in a class definition, but in a method/lambda. So a lambda which receives the request argument and creates the query only then should exactly cover what you need.
EDIT 2
Regarding your edit, the following code has some issues:
def Projects(request):
if request is None:
return Project.objects.none()
return lambda req: Project.objects.filter(deleted__isnull=True, user_fkey=req.user.id)
This either returns an empty object manager, or a callable - but the method Project itself is already a callable, so your ModelChoiceFilter will receive only an object manager when the request object is None, otherwise a lambda, but it is expecting to receive an object manager - it can't iterate over a lambda so it should give you some is not iterable error. So basically you could try:
def project_qs(request):
# you could add some logging here to see what the arguments look like
if not request or not 'user' in request:
return Project.objects.none()
return Project.objects.filter(deleted__isnull=True, user_fkey=request.user.id)
# ...
queryset=project_qs
# ...
As stated in the following thread, you have to pass the request to the filter instance in the view: Customize queryset in django-filter ModelChoiceFilter (select) and ModelMultipleChoiceFilter (multi-select) menus based on request
ex:
myFilter = ReportFilter(request.GET, request=request, queryset=reports)
I have a Physical_therapy_order model and an Event model (an event has foreignkey to Physical_therapy_order). I have a view which allows a user to create a new event. It also has a form with 3 fields from the Physical_therapy_order model.
def PTEventCreateView(request, pt_pk):
#get the pt order and create an a form for that order
pt_order = get_object_or_404(Physical_therapy_order, pk=pt_pk)
ptform = PT_schedule_form(instance=pt_order)
if request.POST:
eventform = PTEventForm(data=request.POST)
ptform = PT_schedule_form(data=request.POST, instance=pt_order)
if eventform.is_valid() and ptform.is_valid():
#I do some checks here that compare data across the two forms.
# if everything looks good i mark keep_saving=True so I can
# continue to save all the data provided in the two forms
if keep_saving:
ptform.save()
eventform.save()
#...send user to succss page
This works just FINE EXCEPT: my PTEvent model has a function attached to its post_save signal. This function pulls the event's related pt_order and makes some modifications to it. Now, if i save the eventform first then the changes from the signal don't happen. if i save the ptform first the ptform changes get discarded and the changes from the signal happen.
THIS IS IMPORTANT: The ptform is editing three entirely different fields than the post_save signal. So its not like they're modifying the same data, only the same model instance. I thought a form only saves the fields in its meta.fields attribute. Why would this be happening? Also, if i save the ptform first, then when eventsform is saved shouldn't the signal use the updated physical_therapy_order? I'm not sure if I'm even on the right track?
I think this is because of cached objects.
What I would suggest is
Save eventform first
Get new instance of pt_order either querying db or through saved instance of eventform
And then re-create form and save.
Sample code change:
# your code
if keep_saving:
evt = eventform.save()
# I'm not sure exact name of your field name for pt_order in Event model, change appropriately
newptform = PT_schedule_form(data=request.POST, instance= evt.pt_order)
newpt = newptform.save()
I have a System that can have one or more Models. I have modeled this relationship in the database with a manytomany field. The code below is for editing the system and its associated methods in a single form.
Adding a new method by filling out its form and pressing submit works only the first time. If I then make a small change and submit again, I get the following message (generated by the code below):
METHODFORMSET.ERRORS: [{}, {'name': [u'Method with this Name already exists.']}]
This is caused by the fact that the name field is unique, but it should have updated, not created a new record, even though I am using the POST data to generate the methodformset instance...
Note that this behaviour only applies to the last appended method instance, not to ones that were already present in the table.
Here is the relevant code, can anyone let me know what I am doing wrong?
def sysedit(request, sys_id):
system = System.objects.get(id=sys_id)
MethodFormSet = modelformset_factory(Method, form=MethodForm)
post = None
if request.POST:
post = request.POST.copy()
if 'add_method' in request.POST:
post['method-TOTAL_FORMS'] = repr(int(
post['method-TOTAL_FORMS'])+ 1)
systemform = SystemForm(data=post, instance=system)
methodformset = MethodFormSet(data=post, prefix='method',
queryset=Method.objects.filter(id__in=system.method.all()))
if methodformset.is_valid():
mfs = methodformset.save()
print 'SAVED-method', mfs
for mf in mfs:
if systemform.is_valid():
sp = systemform.save(mf)
print 'SYSTEM', sp
else:
print 'SYSFORMSET.ERRORS:', systemform.errors
else:
print 'METHODFORMSET.ERRORS:', methodformset.errors
return render_to_response('sysedit.html',
{'systemform': systemform,
'methodformset': methodformset,
'system': system},
context_instance=RequestContext(request))
class System(models.Model):
method = models.ManyToManyField(Method)
...
class Method(models.Model):
name = models.CharField(unique=True)
...
class MethodForm(ModelForm):
class Meta:
model = Method
class SystemForm(ModelForm):
def save(self, new_method=None, commit=True, *args, **kwargs):
m = super(SystemForm, self).save(commit=False, *args, **kwargs)
if new_method:
m.method.add(new_method)
if commit:
m.save()
return m
class Meta:
model = System
exclude = ('method')
[EDIT after Sergzach's answer]:
The problem is not how to deal with the Method with this name already exists error, but to prevent that from occurring in the first place. I think the actual problem may have something to do with the way modelformsets deal with new forms. Somehow it looks like it always tries to create a new instance for the last formset, regardless of whether it already exits.
So if I do not add a new formset after the last one was appended, the modelformset will try to re-create the last one (even though it was just created on the previous submit).
The initial situation is that I have 1 valid Method instance and 1 new unbound instance in the methodformset. I then fill out the form and hit save, which validates both Methods and binds the 2nd one, which is then saved to the table.
So far all is well, but if I then hit save the 2nd time the error occurs. Maybe this has to do with the fact that method-TOTAL_FORMS=2 and method-INITIAL_FORMS=1. Could it be that this causes modelformset to force a create on the 2nd Method?
Can anyone confirm/deny this?
[Edit after a weekend of not looking at the code]:
The problem is caused by the fact that I am saving the forms in the view and after saving, I am sending the original methodformset instance (from before the save) to the template. The problem can be solved by re-instantiating modelformset after the save, using the queryset and NOT the POST data.
So the general rule to prevent errors like this, is either to go to a different page after a save (avoid it altogether), or use the above solution.
Before I post this as THE solution, I need to do more testing.
You can validate each form when saving a formset. I have created a simple example (similar to your code) and it works well for me. It creates new objects if there is no object with a such name otherwise it edits an existing object.
You need a form to edit your model objects:
class EditMethodForm( forms.ModelForm ):
class Meta:
model = Method
exclude = ( 'name', )
Then instead of methodformset.is_valid() you do the next:
for methodform in methodformset:
try:
instance = Method.objects.get( name = request.POST[ 'name' ] )
except Method.DoesNotExist:
methodform.save()
else:
editmethodform = EditMethodForm( request.POST, instance = instance )
if editmethodform.is_valid():
editmethodform.save()
There are some additional features in your code. I show the working principle. Is it enough to understand the solution?
I have solved the problem by re-instantiating modelformset after the save (see edit at the bottom of the question)
For my project I need many "workflow" forms. I explain myself:
The user selects a value in the first field, validates the form and new fields appear depending on the first field value. Then, depending on the others fields, new fields can appear...
How can I implement that in a generic way ?
I think the solution you are looking for is django form wizard
Basically you define separate forms for different pages and customize the next ones based on input in previous screens, at the end, you get all form's data together.
Specifically look at the process step advanced option on the form wizard.
FormWizard.process_step()
"""
Hook for modifying the wizard's internal state, given a fully validated Form object. The Form is guaranteed to have clean, valid data.
This method should not modify any of that data. Rather, it might want to set self.extra_context or dynamically alter self.form_list, based on previously submitted forms.
Note that this method is called every time a page is rendered for all submitted steps.
The function signature:
"""
def process_step(self, request, form, step):
# ...
If you need to only modify the dropdown values based on other dropdowns within the same form, you should have a look at the implemented dajaxproject
I think it depends on the scale of the problem.
You could write some generic JavaScript that shows and hides the form fields (then in the form itself you apply these css classes). This would work well for a relatively small number showing and hiding fields.
If you want to go further than that you will need to think about developing dynamic forms in Django. I would suggest you don't modify the ['field'] in the class like Ghislain suggested. There is a good post here about dynamic forms and it shows you a few approaches.
I would imagine that a good solution might be combining the dynamic forms in the post above with the django FormWizard. The FormWizard will take you through various different Forms and then allow you to save the overall data at the end.
It had a few gotchas though as you can't easily go back a step without loosing the data of the step your on. Also displaying all the forms will require a bit of a customization of the FormWizard. Some of the API isn't documented or considered public (so be wary of it changing in future versions of Django) but if you look at the source you can extend and override parts of the form wizard fairly easily to do what you need.
Finally a simpler FormWizard approach would be to have say 5 static forms and then customize the form selection in the wizard and change what forms are next and only show the relevant forms. This again would work well but it depends how much the forms change on previous choices.
Hope that helps, ask any questions if have any!
It sounds like you want an AJAXy type solution. Checkout the Taconite plugin for jQuery. I use this for populating pulldowns, etc. on forms. Works very nicely.
As for being "generic" ... you might have standard methods on your container classes that return lists of children and then have a template fragmen t that knows how to format that in some 'standard' way.
Ok, I've found a solution that does not use ajax at all and seems nice enough to me :
Create as many forms as needed and make them subclass each other. Put an Integer Hidden Field into the first one :
class Form1(forms.Form):
_nextstep = forms.IntegerField(initial = 0, widget = forms.HiddenInput())
foo11 = forms.IntegerField(label = u'First field of the first form')
foo12 = forms.IntegerField(label = u'Second field of the first form')
class Form2(Form1):
foo21 = forms.CharField(label = u'First field of the second form')
class Form3(Form2):
foo31 = forms.ChoiceField([],
label=u'A choice field which choices will be completed\
depending on the previous forms')
foo32 = forms.IntegerField(label = u'A last one')
# You can alter your fields depending on the data.
# Example follows for the foo31 choice field
def __init__(self, *args, **kwargs):
if self.data and self.data.has_key('foo12'):
self.fields['foo31'].choices = ['make','a','nice','list',
'and you can','use your models']
Ok, that was for the forms now here is the view :
def myview(request):
errors = []
# define the forms used :
steps = [Form1,Form2,Form3]
if request.method != 'POST':
# The first call will use the first form :
form = steps[0]()
else:
step = 0
if request.POST.has_key('_nextstep'):
step = int(request.POST['_nextstep'])
# Fetch the form class corresponding to this step
# and instantiate the form
klass = steps[step]
form = klass(request.POST)
if form.is_valid():
# If the form is valid, increment the step
# and use the new class to create the form
# that will be displayed
data = form.cleaned_data
data['_nextstep'] = min(step + 1, len(steps) - 1)
klass = steps[data['_nextstep']]
form = klass(data)
else:
errors.append(form.errors)
return render_to_response(
'template.html',
{'form':form,'errors':errors},
context_instance = RequestContext(request))
The only problem I saw is that if you use {{form}} in your template, it calls form.errors and so automagically validates the new form (Form2 for example) with the data of the previous one (Form1). So what I do is iterate over the items in the form and only use {{item.id}}, {{item.label}} and {{item}}. As I've already fetched the errors of the previous form in the view and passed this to the template, I add a div to display them on top of the page.