Suppose I have a set of records which I know to be unique based on some other record and an e-mail, thusly:
class Signup(models.Model):
activity = models.ForeignKey(Activity, related_name='activities')
name = models.CharField(max_length=100)
email = models.EmailField()
# many addtional fields here not relevant to question
Then, I have a model form:
class SignupForm(forms.ModelForm):
class Meta:
model = Signup
exclude = [ 'activity' ]
def __init__(self, *args, **kwargs):
self.activity = kwargs.pop('activity')
def save(self, *args, **kwargs):
kwargs['commit'] = False
m = super(SignupForm, self).save(*args, **kwargs)
m.activity = self.activity
m.save()
return m
Suppose a user goes in a fills out the form under the activity, then realizes they made an error in the form, clicks the back button, makes changes, then clicks submit again.
Without any modifications to the code above, a duplicate record for that activity and email would be created.
What I want to know is how I can force the form to update, rather than create, a record if it finds a match for the entered e-mail.
I tried this code:
class SignupForm(forms.ModelForm):
class Meta:
model = Signup
exclude = [ 'activity' ]
def __init__(self, *args, **kwargs):
self.activity = kwargs.pop('activity')
def save(self, *args, **kwargs):
kwargs['commit'] = False
try:
self.instance = Signup.objects.get(email=self.cleaned_data['email'], activity=self.activity)
except Signup.DoesNotExist:
pass
m = super(SignupForm, self).save(*args, **kwargs)
m.activity = self.activity
m.save()
return m
However, looks like this causes the form to ignore all new information for some reason (I have debug toolbar running and examining the query confirms that none of the fields are being changed!)
Is there an accepted way of handling this?
Further request
Is there any way to do this while still using the ModelForm's built-in save function? So far the answers seem to suggest that this is impossible, which is, I'm sorry, ridiculous.
Replace
try:
self.instance = Signup.objects.get(email=self.cleaned_data['email'], activity=self.activity)
except Signup.DoesNotExist:
pass
With:
obj, created = Signup.objects.get_or_create(\
email=self.cleaned_data['email'],
activity=self.activity)
if created:
print 'its a new one, hooray!'
else:
print 'the object exists!'
More information on get_or_create.
Related
I am trying to load a form with user payment options, so this is needing a query set from the users profile.
I have tried initializing the form (below code) with user being required. The issue is if I make self.options when I am initializing. I have also tried creating the choice_field
class ListPaymentOptionsForm(forms.Form):
choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=options)
def __init__(self, user, *args, **kwargs):
self.options = list(UserPaymentOption.objects
.values_list('last_four', 'last_four')
.filter(user=user, active=True))
super(ListPaymentOptionsForm, self).__init__(self, *args, **kwargs)
The above code gives this error:
choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=options)
NameError: name 'options' is not defined
Then I have tried adding the options on the view instead like this
form = ListPaymentOptionsForm(user=request.user)
form.fields['choice_field'].choices = list(UserPaymentOption.objects
.values_list('id', 'last_four')
.filter(user=request.user, active=True))
This causes an error with the form being used on post, it seems like because it is trying to validate the value provided is a choice but in the actual form the choice is not set. The reason I believe this is the problem is this is what the form returns as
form=ListPaymentOptionsForm(request.POST)
print(form)
This returns: Choice field:Select a valid choice. 54 is not one of the available choices.
Any input on this would be very appreciated. Thanks.
Nearly there!
Try doing the fields['choice_field'].choices in the constructor.
class ListPaymentOptionsForm(forms.Form):
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs) # assuming python 3 constructor
self.options = list(UserPaymentOption.objects.values_list('last_four', 'last_four').filter(user=user, active=True))
self.fields['choice_field'] = forms.ChoiceField(widget=forms.RadioSelect, choices=self.options)
Maybe consider having a look at ModelChoiceField instead however, that way you can specify a queryset instead of having to worry about creating a list:
class ListPaymentOptionsForm(forms.Form):
choice_field = forms.ModelChoiceField(widget=forms.RadioSelect, queryset=UserPaymentOption.objects.none())
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['choice_field'].queryset = UserPaymentOption.objects.filter(user=user, active=True)
EDIT based on comments we can use the kwargs to pass the user which may be better:
class ListPaymentOptionsForm(forms.Form):
choice_field = forms.ModelChoiceField(widget=forms.RadioSelect, queryset=UserPaymentOption.objects.none())
def __init__(self, *args, **kwargs):
user = kwargs.pop('user') # this must be done before super()
super().__init__(*args, **kwargs)
self.fields['choice_field'].queryset = UserPaymentOption.objects.filter(user=user, active=True)
Then instantiate the form to handle POST data:
form = ListPaymentOptionsForm(request.POST, user=user)
I have a Django project in which I have a view subclassed from the Django CreateView class. This view is used to upload a file to the server, and uses an UploadedFile model which I have created. The UploadedFile also needs to be associated with a project, which is stored as a ForeignKey called project in the UploadedFile model.
The project id is passed in as part of the URL: (r'^projects/(?P<proj_key>\d+)/$', UploadedFileCreateView.as_view(), {}, 'upload-new')
Because project is not really a form field, I know I need to exclude it using a ModelForm; however, even after I have done so, django never enters the form_valid method (if I put a logging call in it, it will never be written to the log, though logging works fine). I'm guessing that the ForeignKey is the culprit because as far as I can tell it worked before I added that in. I don't understand why django doesn't consider the form to be valid even after I excluded project.
Here is my model definition:
class Project(models.Model):
"""This is a project that is owned by a user and contains many UploadedFiles."""
name = models.CharField(max_length=200)
class UploadedFile(models.Model):
"""This represents a file that has been uploaded to the server."""
STATE_UPLOADED = 0
STATE_ANNOTATED = 1
STATE_PROCESSING = 2
STATE_PROCESSED = 4
STATES = (
(STATE_UPLOADED, "Uploaded"),
(STATE_ANNOTATED, "Annotated"),
(STATE_PROCESSING, "Processing"),
(STATE_PROCESSED, "Processed"),
)
status = models.SmallIntegerField(choices=STATES,
default=0, blank=True, null=True)
file = models.FileField(upload_to=settings.XML_ROOT)
project = models.ForeignKey(Project)
def __unicode__(self):
return self.file.name
def name(self):
return os.path.basename(self.file.name)
def save(self, *args, **kwargs):
if not self.status:
self.status = self.STATE_UPLOADED
super(UploadedFile, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
os.remove(self.file.path)
self.file.delete(False)
super(UploadedFile, self).delete(*args, **kwargs)
class UploadedFileForm(forms.ModelForm):
class Meta:
model = UploadedFile
excludes = ('project',)
Here is my view definition:
class UploadedFileCreateView(CreateView):
model = UploadedFile
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.project_id = self.kwargs['proj_key']
self.object.save()
f = self.request.FILES.get('file')
data = [{'name': f.name,
'url': settings.MEDIA_URL + "files/" + f.name.replace(" ", "_"),
'project': self.object.project.get().pk,
'delete_url': reverse('fileupload:upload-delete',
args=[self.object.id]),
'delete_type': "DELETE"}]
response = JSONResponse(data, {}, response_mimetype(self.request))
response['Content-Disposition'] = 'inline; filename=files.json'
return super(UploadedFileCreateView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(UploadedFileCreateView, self).get_context_data(**kwargs)
return context
I see two likely problems:
1) The form keyword is exclude, not excludes.
Generally the recommendation is to favor explicitly listing fields to be included, so you don't accidentally expose any fields you might later add, but exclude will work.
2) You're not actually using your custom form class in the view. Set the form_class attribute to UploadedFileForm.
I have a simple form,
class Compose(forms.Form):
CHOICES = ()
recepient = forms.ChoiceField(choices=CHOICES)
subject = forms.CharField(max_length=100)
message = forms.CharField(widget=forms.Textarea)
Chocies are generated as
def mychoiceview(request):
subscribed_lists, other_lists = getListOfLists(request.user.email)
for lst in subscribed_lists:
# list name and list address
CHOICES = CHOICES + ((lst[1],lst[0]),)
#Bind data to form and render
Basically, the user is subscribed to certain lists (from a superset of lists) and can choose (via dropdown) which list he/she wants to send the message to.
The problem is that I cannot find how to bind the "CHOICES" to the django form.
Some solutions online include using models.. but I don't have a queryset... just a dynamically generated tuple of ids I want the choicefield to render.
Any ideas ?
Thanks !
#nnmware's solution is correct, but a little too complex/confusing. Below is a more succinct version that works just as well:
class DynamicBindingForm(forms.Form):
def __init__(self, *args, **kwargs):
super(DynamicBindingForm, self).__init__(*args, **kwargs)
self.fields['recipient'] = forms.ChoiceField(choices=db_lookup_choices())
where db_lookup_choices is a call to some database or other set of dynamic data and returns a list of pairs for choices: [('val', 'Label'),('val2', 'Label2')]
If you using Class-based view, then:
in view make mixin for sending request in form
class RequestToFormMixin(object):
def get_form_kwargs(self):
kwargs = super(RequestToFormMixin, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
class YouView(RequestToFormMixin, CreateView):
model = YouModel
# etc
in form make mixin for receive request from view
class RequestMixinForm(forms.Form):
def __init__(self, *args, **kwargs):
request = kwargs.pop('request')
super(RequestMixinForm, self).__init__(*args, **kwargs)
self._request = request
class Compose(RequestMixinForm):
subject = forms.CharField(max_length=100)
message = forms.CharField(widget=forms.Textarea)
def __init__(self, *args, **kwargs):
super(Compose, self).__init__(*args, **kwargs)
subscribed_lists, other_lists = getListOfLists(self._request.user.email)
for lst in subscribed_lists:
# list name and list address
CHOICES = CHOICES + ((lst[1],lst[0]),)
self.fields['recipient'] = forms.ChoiceField(choices=CHOICES)
I have a custom form... I'd like to auto save the author (authenticated user) for the form data. I'm using ModelForm for creating the form.
models.py
class Tracker(models.Model):
client = models.ForeignKey(Clients)
user = models.ForeignKey(User)
description = models.TextField()
...
I have also linked the custom profile to the django users table... also the auth works fine... I can read the user and id...
forms.py
class TrackerForm(ModelForm):
def __init__(self, *args, **kwargs):
super(TrackerForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['date_job_start','date_job_end','description','onsite','billable','client']
class Meta:
model = Tracker
widgets = {
'client': HiddenInput(),
}
When the form is created from the upper class and I try to save it... it wants the user data (missing warning). How can I change it so that it would automatically save the authenticated user instead of asking for it?? I know that I dont' have the user field defined here... that's because I don't want a dropdown for it... I want the user to be saved from the auth...without any selection or display...
P.S.: I know about the initial option... there must be a better way?
Thanks!
BR
class TrackerForm(ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(TrackerForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['date_job_start','date_job_end','description','onsite','billable','client']
class Meta:
model = Tracker
widgets = {
' client': HiddenInput(),
}
exclude = ('user',)
def save(self):
obj = super(TrackerForm, self).save(commit=False)
obj.user = self.user
obj.save()
return obj
And in view:
form = TrackerForm(user=request.user) #and other parameters
I am trying to save a modelform that represents a bank account but I keep getting a ValueError even though the form appears to validate. The models I have to use are:
class Person(models.Model):
name = models.CharField()
class Bank(models.Model):
bsb = models.CharField()
bank_name = models.CharField()
def __unicode__(self):
return '%s - %s', (self.bank_name, self.bsb)
def _get_list_item(self):
return self.id, self
list_item = property(-get_list_item)
class BankAccount(models.Model):
bank = models.ForignKey(Bank)
account_name = models.CharField()
account_number = models.CharField()
class PersonBankAcc(models.Model):
person = models.ForeignKey(Person)
The ModelForm for the personBankAcc;
def PersonBankAccForm(forms.ModelForm):
bank = forms.ChoiceField(widget=SelectWithPop)
class Meta:
model = PersonBankAcct
exclude = ['person']
def __init__(self, *args, **kwargs):
super(PersonBankAccForm, self).__init__(*args, **kwargs)
bank_choices = [bank.list_item for banks in Bank.objects.all()]
bank_choices.isert(0,('','------'))
self.fields['bank'].choices = bank_choices
The view is:
def editPersonBankAcc(request, personBankAcc_id=0):
personBankAcc = get_object_or_404(PersonBankAcc, pk=personBankAcc_id)
if request.method == 'POST':
form = PersonBankAccForm(request.POST, instance=personBankAcc )
if form.is_valid():
print 'form is valid'
form.save()
return HttpResponseRedirect('editPerson/' + personBankAcc.person.id +'/')
else:
form = PersonBankAccForm(instance=personBankAcc )
return render_to_response('editPersonBankAcc', {'form': form})
When I try to save the form I get the a VlaueError exception even though it gets passed the form.is_valid() check, the error I get is:
Cannot assign "u'26'": PersonBankAcc.bank must be a "bank" instance
I know the issue is arising because of the widget I am using in the PersonBankAccForm:
bank = forms.ChoiceField(widget=SelectWithPop)
because if I remove it it works. But all that does is gives me the ability to add a new bank to the database via a popup and then inserts it into the select list, similar to the admin popup forms. I have checked the database and the new bank is added. But it fails even if I don't change anything, if I call the form and submit it, I get the same error.
I don't understand why it does not fail at the is_valid check.
Any help would be greatly appreciated.
Thanks
Andrew
better yet, i don't think it really needs to be in the init function...
def PersonBankAccForm(forms.ModelForm):
bank = forms.ModelChoiceField(queryset=Bank.objects.all(),widget=SelectWithPop(),empty_label='-----')
class Meta:
model = EmpBankAcct
exclude = ['person']
def __init__(self, *args, **kwargs):
super(PersonBankAccForm, self).__init__(*args, **kwargs)