django request.POST field name substitution - django

I am using ajax to send data into a django view with data coming in via request.POST. I am posting the model field that needs to be updated as well as the model value. I just need to know how to use the field name variable I extract from request.POST['field_name'] so I can set the field in the model. Here is my code.
field_name = request.POST["field_name"]
field_value = request.POST["field_value"]
member_id = get_member_session(request).id
try:
member = Members.objects.get(id=member_id)
except:
status="ERROR-USER-DOES-NOT-EXIST"
return json_status(status)
try:
member.field_name=field_value
member.save()
return json_status('OK')
except:
status = "USER_SAVE_ERROR"
return json_status(status)
member.field_name is obviously not right. Do I need to use eval(field_name) or something like that? I would prefer not to if possible.
Many thanks
Rich

Use setattr, which allows you to set a variable attribute on an object:
try:
member._meta.get_field(field_name)
except member.FieldDoesNotExist:
# return something to indicate the field doesn't exist
return json_status('USER_FIELD_ERROR')
setattr(member, field_name, field_value)
member.save()
return json_status('OK')
edit: I updated to use model._meta.get_field, as it's a better approach. Mentioned in this answer for another question.

Related

ModelForm.is_valid() is False when row already exists. How to get the object?

My Location model has a unique filed called name. Through LocationForm I get user input and try to either insert it into the database or get the axisting object. But if user inputs an existing name, lf.is_valid() becomes False and I never get to the get_or_create command. Any idea how I can pass this?
lf = LocationForm(request.POST or None)
if lf.is_valid():
location_instance, created = Location.objects.get_or_create(**lf.cleaned_data)
Thanks,
If you're using ModelForm then maybe you can try overriding _get_validation_exclusions method.
https://github.com/django/django/blob/8838d4dd498c8f66ea4237fe8a79a5f77d6f95c9/django/forms/models.py#L297
class LocationForm():
def _get_validation_exclusions(self):
exclude = super(LocationForm, self)._get_validation_exclusions()
exclude.append(self.fields['name'])
return exclude
Let me know how that goes..
Thanks for your thought.
I found a useful description of .is_valid in Django documentation. Django Validating Objects.
Apparently .is_valid also check against Model.validate_unique().
Here is what I ended up doing:
if lf.is_valid():
location_instance = lf.save()
return render (...)
else:
location_instance, created = Location.objects.get_or_create(**lf.cleaned_data)
error_msg = lf.errors["__all__"]
return render(...)

How to use related fields (fields.related) in odoo-8?

I am trying to retrieve comment field(customer internal notes) from res_partner to account invoice module.Right now I just want to print it later I will include it in xml code. I tried in three ways like this,
1)comment2 = fields.Char(string='Comment',related='res_partner.comment',compute='_compute_com')
#api.multi
def _compute_com(self):
print self.comment2
2)comment = fields.Many2one('res.partner','Comment',compute='_compute_com')
#api.multi
def _compute_com(self):
print self.comment
3)partner_comment = fields.Char(compute='_compute_com')
#api.multi
def _compute_com(self):
Comment = self.env['res.partner'].browse(partner_id).comment
print Comment
You should use a related field instead:
comment = fields.Char(related='partner_id.comment')
If you need to store it in your account_invoice record you also need to add the parameter store=True
Problem is, this way you can't just print it but if you need to show it you need to put it into your view.
If you really need to print it temporarly you need to do this other way:
comment = fields.Char(compute='_compute_comment')
def _compute_comment(self):
for record in self:
record.comment = partner_id.comment
print record.comment
Related Field
There is not anymore fields.related fields.
Instead you just set the name argument related to your model:
participant_nick = fields.Char(string='Nick name',
related='partner_id.name')
The type kwarg is not needed anymore.
Setting the store kwarg will automatically store the value in database. With new API the value of the related field will be automatically updated, sweet.
participant_nick = fields.Char(string='Nick name',
store=True,
related='partner_id.name')
Note
When updating any related field not all translations of related field
are translated if field is stored!!
Chained related fields modification will trigger invalidation of the cache for all elements of the chain.
in odoo8
if need same object fields to related then you can use related="related field name " use store=True
comment2 = fields.Char(string='comment',related='comment', store=True)
LINK
Be careful, you have to use the same kind of field !!
(I had the problem with Selection and Char ... so I have to use Selection with Selection)

Django: Adding objects to a related set without saving to DB

I'm trying to write an internal API in my application without necessarily coupling it with the database.
class Product(models.Model):
name=models.CharField(max_length=4000)
price=models.IntegerField(default=-1)
currency=models.CharField(max_length=3, default='INR')
class Image(models.Model):
# NOTE -- Have changed the table name to products_images
width=models.IntegerField(default=-1)
height=models.IntegerField(default=-1)
url=models.URLField(max_length=1000, verify_exists=False)
product=models.ForeignKey(Product)
def create_product:
p=Product()
i=Image(height=100, widght=100, url='http://something/something')
p.image_set.add(i)
return p
Now, when I call create_product() Django throws up an error:
IntegrityError: products_images.product_id may not be NULL
However, if I call p.save() & i.save() before calling p.image_set.add(i) it works. Is there any way that I can add objects to a related object set without saving both to the DB first?
def create_product():
product_obj = Product.objects.create(name='Foobar')
image_obj = Image.objects.create(height=100, widght=100, url='http://something/something', product=product_obj)
return product_obj
Explanation:
Product object has to be created first and then assign it to the Image object because id and name here is required field.
I am wondering why wouldn't you not require to make a product entry in DB in first case? If there is any specific reason then i may suggest you some work around?
EDIT: Okay! i think i got you, you don't want to assign a product to an image object initially. How about creating a product field as null is equal to true.
product = models.ForeignKey(Product, null=True)
Now, your function becomes something like this:
def create_product():
image_obj = Image.objects.create(height=100, widght=100, url='http://something/something')
return image_obj
Hope it helps you?
I got same issue with #Saurabh Nanda
I am using Django 1.4.2. When I read in django, i see that
# file django/db/models/fields/related.py
def get_query_set(self):
try:
return self.instance._prefetched_objects_cache[rel_field.related_query_name()]
except (AttributeError, KeyError):
db = self._db or router.db_for_read(self.model, instance=self.instance)
return super(RelatedManager,self).get_query_set().using(db).filter(**self.core_filters)
# file django/db/models/query.py
qs = getattr(obj, attname).all()
qs._result_cache = vals
# We don't want the individual qs doing prefetch_related now, since we
# have merged this into the current work.
qs._prefetch_done = True
obj._prefetched_objects_cache[cache_name] = qs
That 's make sese, we only need to set property _prefetched_objects_cache for the object.
p = Product()
image_cached = []
for i in xrange(100):
image=Image(height=100, widght=100, url='http://something/something')
image_cached.append(image)
qs = p.images.all()
qs._result_cache = image_cached
qs._prefetch_done = True
p._prefetched_objects_cache = {'images': qs}
Your problem is that the id isn't set by django, but by the database (it's represented in the database by an auto-incremented field), so until it's saved there's no id. More about this in the documentation.
I can think of three possible solutions:
Set a different field of your Image model as the primary key (documented here).
Set a different field of your Production model as the foreign key (documented here).
Use django's database transactions API (documented here).

Django ModelChoiceField: filtering query set and setting default value as an object

I have a Django Form class defined likes this in Models:
class AccountDetailsForm(forms.Form):
...
adminuser = forms.ModelChoiceField(queryset=User.objects.all())
This works OK, but it has some limitations I can't seem to work around:
(1) I would like to use a filter on the queryset, based on a variable accountid passed to the form, like this:
User.objects.filter(account=accountid)
This can't work in the model because accountid can't be passed as a variable, of course.
It follows that the queryset must somehow be defined in the Views, but as far as I can see it's a required field in the Form class.
(2) I would like to make the default choice of AccountDetailsForm an object in the database, which I can select in the Views like this:
User.objects.filter(account=accountid).filter(primary_user=1)
I've tried specifying the adminuser as a default value in the form, (which works with other standard form fields, like CharField):
adminuser = User.objects.filter(account=accountid).filter(primary_user=1)
...
form = AccountDetailsForm({'adminuser': adminuser})
return render_to_response('accounts/edit/accountdetails.html',
{'form': form, 'account':account})
But no luck.
Should I be using something other than ModelChoiceField given the flexibility I need here?
Thanks.
Override the init method and accept a new keyword argument
class AccountDetailsForm(forms.Form):
...
adminuser = forms.ModelChoiceField(queryset=User.objects.all())
def __init__(self, *args, **kwargs):
accountid = kwargs.pop('accountid', None)
super(AccountDetailsForm, self).__init__(*args, **kwargs)
if accountid:
self.fields['adminuser'].queryset = User.objects.filter(account=accountid)
form = AccountDetailsForm(accountid=3)
You can always just set the choices manually in the view as well.
form = AccountDetailsForm()
form.fields['adminuser'].queryset = User.objects.filter(account=accountid)
Be warned: you are not setting default values by passing in a dictionary to a form like in your example.
You are actually creating a Bound Form, potentially triggering validation and all that jazz.
To set defaults, use the initials argument.
form = AccountDetailsForm(initial={'adminuser':'3'})
You can override the field in the view
yourForm = AccountDetailsForm()
yourForm.fields['accomodation'] = forms.ModelChoiceField(User.objects.filter(account=accountid).filter(primary_user=1))
Something that hasn't been mentioned here yet is the Form.clean() method. This method is specifically for custom validation.
For your example, you could do something like this:
class AccountDetailsForm(forms.Form):
adminuser = forms.ModelChoiceField(queryset=User.objects.all())
account_id = forms.IntegerField() # or ModelChoiceField if that applies
def clean(self):
account_id = self.cleaned_data['account_id']
self.cleaned_data['adminuser'] = User.objects.filter(account_id=account_id)
return self.cleaned_data
The clean() method gets called after the default clean methods, so you can use self.cleaned_data (same as form.cleaned_data in the view) and return it however you'd like.
Even better, you can name the method according to the field you'd like to clean (def clean_adminuser) and make easier to read.
def clean_adminuser(self):
account_id = self.cleaned_data['account_id']
return User.objects.filter(account_id=account_id)
Also in this method you can call Form.add_error() if there are any issues you want to handle.
In Django 2.0 you can pass object (User in your case) from the view to the form like this (you have to retrieve obj from the DB first):
form = AccountDetailsForm(initial={'adminuser': adminuser})
It will give you a default selected object (answers your 2) question)

Django get_FIELD_display

I am trying to access data.get_age_display in my email template. I can't seem to get the display of this. I am not sure what I am doing wrong, I've using get_FIELD_display numerous times before but passed as context to a normal template. Is there something different with forms?
class RequestForm(forms.Form):
ADULT = 1
SENIOR = 2
STUDENT = 3
AGE_GROUP = (
(ADULT, 'Adult'),
(SENIOR, 'Senior'),
(STUDENT, 'Student'),
)
name = forms.CharField(max_length=255)
phone = forms.CharField(max_length=15)
age = forms.ChoiceField(choices=AGE_GROUP)
details = forms.CharField(widget=forms.Textarea())
def save(self):
order = Order(
name = self.cleaned_data['name'],
phone = self.cleaned_data['phone'],
age = self.cleaned_data['age'],
details = self.cleaned_data['details'],
)
order.save()
template = loader.get_template('request_email.txt')
# send over the order object in an email extracted so they can handle the ticket order
context = Context({
'data': order,
})
#import pdb; pdb.set_trace()
email_subject = 'Request Tickets'
mail_managers(email_subject, template.render(context))
in my request_email.txt all I am doing is {{ data.get_age_display }} any ideas?
Jeff
You haven't shown the code for the Order model that you're creating. Are you sure that the age field on the model has choices set?
Any reason you're not using a ModelForm? You're creating an Order object within the form's save() method, but not returning it. A modelform would do that for you, as well as removing the need to redeclare the fields for the form.
I know this is coming WAAAAAY later than the question being posted but here's my answer for completeness and anyone else who might benefit from it :-)
I'm going to assume that in AGE_GROUP, ADULT, SENIOR and STUDENT are integers. Your form cleaning will NOT automatically clean the string contained in the POST and return an integer. So in this code:
context = Context({
'data': order,
})
you would think order.age is referring to an integer but that is, in fact, incorrect. It's burned me a few times before because this will correctly save the integer to the physical table, but the order instance still has the string representation of the age field.
You could do one of two things:
1. Clean this in the field:
clean_age(self):
return int(self.cleaned_data['age'])
or create a new field type:
def MyChoiceField(forms.ChoiceField):
def clean(self, value):
if not value:
if self.required:
raise forms.ValidationError(self.error_messages['required'])
return None
else:
return None
return int(value)
link that to the form field:
age = MyChoiceField(choices=AGE_GROUP)
and then you'll be able to apply this logic to any other such choice field in future. Personally, I find the latter approach the best one and I stick all my custom field types into a form_utils file so that I can use them everywhere. Another gotcha is that forms.charField doesn't automatically strip the entered text and you can use this approach to fix that too.