how to make #api.depends trigger a function when another model's field changes - odoo-11

i'm new in odoo dev and i'm trying to make the field value 'recommanded_price' (from marketplace.marketplaceinfo model) change when 'formula' field changes (which is in the marketplace.marketplace model)
I used the #api.depends decorator but it doesn't trigger the function
Here's the essential of my code
class marketplace(models.Model):
_name = 'marketplace.marketplace'
_description = 'Marketplace'
formula = fields.Char('Formula')
product_ids = fields.One2many('marketplace.marketplaceinfo', 'mkp', 'Products')
class marketplace_info(models.Model):
_name = 'marketplace.marketplaceinfo'
_description = 'Information about a product marketplace'
mkp = fields.Many2one('marketplace.marketplace','Marketplace',ondelete = 'cascade',required = True,
help='Marketplace of this product'
)
product_tmpl_id = fields.Many2one(
'product.template', 'Product Template',
index=True, ondelete='cascade')
price = fields.Float('Price', default=0.0, digits=dp.get_precision('Product Price'),
help="The selling price for a product within the marketplace")
recommonded_price = fields.Float('Recommended Price', compute='_compute_recommonded_price' ,default=0.0, digits=dp.get_precision('Product Price'),
readonly = True, help="The recommended selling price for a product within the marketplace")
#api.depends('mkp.formula')
def _compute_recommonded_price(self):
#do something

#api.depends
This decorator will trigger the call to the decorated function if any of the fields specified in the decorator is altered by ORM or changed in the form:
#api.depends('name', 'an_other_field')
def afun(self):
pass
Other then you can use another decorator
#api.onchange
This decorator will trigger the call to the decorated function if any of the fields specified in the decorator is changed in the form:
#api.onchange('fieldx')
def do_stuff(self):
if self.fieldx == x:
self.fieldy = 'toto'
Difference :
In previous sample self corresponds to the record currently edited on the form. When in on_change context all work is done in the cache. So you can alter RecordSet inside your function without being worried about altering database. That’s the main difference with #api.depends
REF : enter link description here

Related

Combining AJAX with ModelMultipleChoiceField and custom form

I've been banging my head for a while and could not find a similar issue.
I'll go over my code
Model
class RestauranteMenu(models.Model):
restaurante = models.ForeignKey(RestauranteUser)
name = models.CharField(max_length=60, blank=False)
class OpeningHours(models.Model):
...
restaurante = models.ForeignKey(RestauranteUser)
menu = models.ForeignKey(RestauranteMenu, blank=True, null=True)
...
Form
class MenuForm(ModelForm):
'''
View = menus(request)
Template = pages/menus.html
'''
horario = ModelMultipleChoiceField(queryset=OpeningHours.objects.all())
def save(self, restaurante, horario, commit=True):
#Linking relationship Restaurant x RestaurantMenu
menu = super(MenuForm, self).save(commit=False)
menu.restaurante = restaurante
if commit:
menu.save()
#Linking relationship RestaurantMenu x OpeningHours
horario = OpeningHours.objects.filter(id=horario, restaurante = restaurante).first()
if horario:
horario.menu = menu
horario.save()
return menu
class Meta:
model = RestauranteMenu
exclude = ['restaurante']
view
def menus(request):
#verify if its an update.
instance = request.POST.get('instance')
if instance not in [None, '']:
menu = RestauranteMenu.objects.get(id=instance)
form = MenuForm(request.POST or None, instance=menu, initial={'horario': OpeningHours.objects.filter(restaurante=request.user).values_list('id', flat=True)})
else:
form = MenuForm(request.POST or None, initial={'horario': OpeningHours.objects.filter(restaurante=request.user).values_list('id', flat=True)})
if request.POST:
if form.is_valid():
try:
#When saving, we pass a restaurant reference and OpeningHours reference.
form.save(request.user, form.data.get('horario'))
JS
$(document).on("click", "#sendmenuform", function() {
var horariosId = [];
$('#horario :selected').each(function(i, selected) {
horariosId.push($(selected).val());
});
$.ajax({
type: "POST",
url: "../menus/",
data: {
name : $('[name="name"]').val(),
horario : horariosId,
instance : $('#sendmenuform').attr("data-id"),
csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val()
},
success : function(data) {
.... process response...
}
});
What's the issue
Based on my models, I want a Menu to have a ManyToOne relationship with OpeningHours, that means that one Menu can be at different OpeningHours.
When I'm submitting my form (via AJAX), I'm not able to populate the field 'horario' inside the form. When instantiating the form, I'm filling the field with a queryset that will filter by that Restaurant.
On the html, I have a select multiple, so that the restaurant is able to link one Menu to several different OpeningHours object.
I don't know how I'm supposed to process the information sent by the AJAX request to the view, specially this ModelMultipleChoiceField. Do I need to override any of the forms method?
ModelMultipleChoiceField expects model objects and not arbitrary strings. Hence using the values_list query set will only land you into trouble. Your form will not validate.
For your use case, use ChoiceFields. You can populate them with a string by overriding your __init__ method. For example, in your Forms.py
horario = ChoiceField(
choices[],)
def __init__(self, *args, **kwargs):
super(MenuForm, self).__init__(*args, **kwargs)
self.fields['horario'].choices = [(x.id, x.modelfieldtodisplay) for x in OpeningHours.objects.all()]
If you want to do this when you post the form or load it do it inside a view that is triggered by a listener for the event. You'll have to write Javascript to handle this similar to this tutorial but this asks you to use ModelChoiceField which I do not recommend for you because it doesn't work gracefully when you're trying to dynamically populate multiple fields and submit the form, validate it and run some operations on the data.
Instead, I implore you to take a look at Dajax and Dajaxice which takes altogether 5 minutes to set up and handles forms and AJAX effortlessly making your job significantly simpler. I do emphasize though, void using ModelChoiceField for you use case. I learnt that the hard way.

Django ChoiseField validation

I have simple form witch has options for delivery; pickup and post it. There is also possibility to use discount coupons. When using coupons user will always have to go and pay in person with the coupons.
My problem is that when trying to force this behavior and change radio choices when using coupongs in form clean it will change form.cleaned_data but view will render form with original selection.
#forms.py
class DeliveryOptionsForm(forms.Form):
RADIO_CHOICES = (
('pickup',"Pick your stuff from office")),
('postit', "send to me by mail (+2€)"),
)
delivery = forms.ChoiceField(widget=forms.RadioSelect,
choices=RADIO_CHOICES,
help_text=_('Select shipping method'))
discount = forms.DecimalField(help_text=_('DISCOUNT TICKET'), required=False)
def clean(self):
data = self.cleaned_data
if 'discount' in data:
if data['discount'] == None:
data['discount'] = 0;
else:
#force pickup when putting discount coupongs
data['pickup'] = 'pickup'
return data
And my view
def purchase_confirmation(req):
cart = req.session['cart']
form = DeliveryOptionsForm(req.POST or None, initial={'pickup': 'postit'})
if req.method == 'POST' and form.is_valid():
discount = form.cleaned_data['discount']
pickup = form.cleaned_data['pickup']
# pickup will be correctly set, for logic,
# but form will render selection unchanged
#Some logic here
....
if 'confirm' in req.POST:
return redirect('shop-generating_bill')
if 'update' in req.POST:
pass
return render(req,"confirmation.html",{'form' : form})
Not sure if this is the cause of your issue but, in your clean method you should get the cleaned data by calling the clean method of the super class.
cleaned_data = super(DeliveryOptionsForm, self).clean()

Django: Displaying a form from one model with content from a second model on one page view

I have an irksome little problem with a forum that I am building.
I need to generate a page that contains a form to populate one
model but that page should also display an entry from another related model.
I want the form to populate a new response in the model Responses (see code below).
That model has the model StartMsg as a foreign key. I want the page view (response_form) to display StartMsg.msg that the user is responding to. The problem is that I am using django's built in forms to generate the form and render_to_response to call the page. The render_to_response statement (marked (A)) sends a dictionary containing the form components from the Responses model to the html template.
How do I include info about the model StartMsg into the render_to_response statement (marked
with (A), below)? Is there a better way to accomplish what I am after?
Here are the models:
class StartMsg (models.Model):
msg_title = models.TextField(max_length=500)
msg = models.TextField(max_length=2000)
pub_date = models.DateTimeField('date published')
author = models.ForeignKey(User)
def __unicode__(self):
return self.msg
class Responses (models.Model):
startmsg = models.ForeignKey(StartMsg) #one startmsg can have many responses
response = models.TextField()
responder = models.ForeignKey(User)
pub_date = models.DateTimeField('date published')
def __unicode__(self):
return self.response
Below is the form processing function followed by the form model.
def response_form (request, msg_id):
msg = get_object_or_404(StartMsg, pk=msg_id)
form = MsgRespForm(request.POST or None)
if form.is_valid():
new_rspns = form.save(commit =False)
#retrieve StartMsg entry to assign to the response entry foreign key
message = StartMsg.objects.get(pk=msg_id)
new_rspns.startmsg = message
response = form.cleaned_data['response']
new_rspns.response = response
new_rspns.responder= request.user.username()
new_rspns.pub_date = datetime.now()
new_rspns.save()
return HttpResponseRedirect(reverse('forum.views.forum', )) #if form is processed, view goes here
return render_to_response( #if form not processed, view goes here
'forumresponseform.html',
{'form': form}, (A)
context_instance = RequestContext(request)
)
class MsgRespForm(forms.ModelForm):
# Add Labels to form fields:
response = forms.CharField(label='Your Response',
widget=forms.Textarea(attrs={'cols': 60, 'rows': 10}))
class Meta: #Define what fields in the form
model = Responses
fields = ('response',)
I found a solution that seems to work.
You can make a function that builds a dictionary out of a variable number of model queries then pass that dictionary to the form template on line (A).
def build_dict(request, ** kwargs):
d = dict(user=request.user, ** kwargs)
return d
msg = get_object_or_404(StartMsg, pk=msg_id)
form = MsgRespForm(request.POST or None)
dict = build_dict(request, msg=msg, form=form)
return render_to_response( #if form not processed, view goes here
'forumresponseform.html',
{'form': dict}, (A)
context_instance = RequestContext(request)
)

How to subclass django's generic CreateView with initial data?

I'm trying to create a dialog which uses jquery's .load() function to slurp in a rendered django form. The .load function is passed the pk of the "alert" object. Also available in the class functions are things like self.request.user so I can pre-fill those fields, shown below in the Message model (models.py):
class Message(models.Model):
user = models.ForeignKey(User)
alert = models.ForeignKey(Alert)
date = models.DateTimeField()
message = models.TextField()
Subclassing django's CreateView makes it pretty easy to generate a context with an instance of the ModelForm (views.py):
class MessageDialogView(CreateView):
""" show html form fragment """
model = Message
template_name = "message.html"
def get_initial(self):
super(MessageDialogView, self).get_initial()
alert = Alert.objects.get(pk=self.request.POST.get("alert_id"))
user = self.request.user
self.initial = {"alert":alert.id, "user":user.id, "message":"test"}
return self.initial
def post(self, request, *args, **kwargs):
super(MessageDialogView, self).post(request, *args, **kwargs)
form_class = self.get_form_class()
form = self.get_form(form_class)
context = self.get_context_data(form=form)
return self.render_to_response(context)
The problem here is that self.initial does not get rendered with the form. I have insured that the form is indeed calling get_initial and the form instance has the proper initial data in post, but when the form is rendered in the template message.html it doesn't grab any of the initial data like I would expect. Is there a special trick to get this to work? I've scoured the docs (seems to be lacking examples on generic based class views) and source but I can't see what I'm missing.
get_initial() should just return a dictionary, not be bothered with setting self.initial.
Your method should look something like this:
def get_initial(self):
# Get the initial dictionary from the superclass method
initial = super(YourView, self).get_initial()
# Copy the dictionary so we don't accidentally change a mutable dict
initial = initial.copy()
initial['user'] = self.request.user.pk
# etc...
return initial
you can use like :
from django.shortcuts import HttpResponseRedirect
class PostCreateView(CreateView):
model = Post
fields = ('title', 'slug', 'content', 'category', 'image')
template_name = "create.html"
success_url = '/'
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save()
return HttpResponseRedirect(self.get_success_url())
that's work for me
(Edited because what you're trying does actually work)
I ran into the same problem yesterday, but it's working now – I think I was returning an object instead of a dict in get_initial.
In terms of fixing your problem, I'm a little suspicious of how much you seem to be doing in post() – could you try it with the default (non-overrided) post()?
You could also use pdb (or print statements) to check the value of self.get_form_kwargs make sure that initial is being set.

Django Ajax field help

I have a Django application where I'm trying to have a form that populates a drop down dynamically based on a previous dropdown.
simplified Models:
class VehicleMake(models.Model):
make = models.CharField(max_length = 30)
class VehicleModel(models.Model):
model = models.CharField(max_length = 80)
make = models.ForeignKey(VehicleMake)
class Listing(models.Model):
make = models.ForeignKey(VehicleMake)
model = models.ForeignKey(VehicleModel)
Form:
class DynamicChoiceField(ModelChoiceField):
def clean(self, value):
return value
class MyForm(ModelForm):
category = ModelChoiceField(VehicleCategory.objects, widget=forms.Select(attrs={'onchange':'FilterMakes();'}))
make = DynamicChoiceField(VehicleMake.objects,widget=forms.Select(attrs={'disabled':'true','onchange':'FilterModels();'}), empty_label="Select Model")
model = DynamicChoiceField(VehicleModel.objects,widget=forms.Select(attrs={'disabled':'true'}), empty_label="Select Make")
class Meta:
model = Listing
View:
def new_listing(request):
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
form.save()
else:
form = MyForm()
return render_to_response("newlisting.html", {
"form": form,'model_id':model_id,'make_id':make_id
})
I also have some ajax defined for the auto-populate but this is not the problem
When I submit the form I get the following:
Cannot assign "u'2'": "Listing.make" must be a "VehicleMake" instance.
if I try
make=VehicleMake.objects.get(pk=request.POST['make'])
form.fields['make'] = make
then I get
'VehicleMake' object has no attribute 'widget'
After the suggestion of one of the commenter's that the DynamicChoiceField class was the culprit I removed it and set the form objects for ModelChoiceFields with the exact same other parameters. The object appears to pass and validate correctly as well. The extra class existed based on an old tutorial I found. it appears that what the author did there works with the forms.ChoiceField but is not required for using a ModelChoiceField
thanks everyone for the help

Resources