Reputation: 726
I have two different database connections defined in my settings.py
('default' and 'banco1').
Then in some view I receive a POST containing some data that I want to validate against a ModelForm
that I create:
product_form = ProductForm(request.POST)
if product_form.is_valid():
The product model:
class Product(models.Model):
name = models.CharField(max_length=125, blank=False, null=False)
description = models.CharField(max_length=255, blank=True)
abbreviation = models.CharField(max_length=15, blank=True)
category = models.ForeignKey('inventory.Category',
on_delete=models.SET_NULL, null=True, blank=True)
metric = models.CharField(max_length=15, blank=True, null=True)
The Form:
class ProductForm(ModelForm):
class Meta:
model = Product
fields = ['name', 'description', 'abbreviation', 'category', 'metric']
The view:
def create(request):
if request.method != 'POST':
return JsonResponse('Operação inválida', safe=False)
product_form = ProductForm(request.POST)
if product_form.is_valid():
f = product_form.save(commit = False)
f.save(using=request.session['database'])
return HttpResponse("Novo product salvo com sucesso")
else:
return HttpResponse("Impossível salvar product. Verifique os campos")
The Product model is suposed to be saved always in the 'banco1' database, so the FK validations should be queried from the 'banco1' database, but whenever I try to validade with product_form.is_valid()
Django tries to query the 'default' database and throws a validation error as the FK constraint can't be satisfied in the 'default' database.
How can I set a model form to make his validations agains a manually selected database?
EDIT
Was suggested to me to utilize a DB ROUTER schema to being able to select the proper database config, which works fine. My problem is that this selection should be made based in some criteria that not just the model. How can I pass some variables from the view to the db_for_read
method in my db router class?
EDIT2
New ProductForm Class
class ProductForm(ModelForm):
class Meta:
model = Product
fields = ['name', 'description', 'abbreviation', 'category', 'metric']
def __init__(self,database):
category = forms.ModelChoiceField(queryset=Category.objects.using(database).all())
trying to instantiate the form inside my view like this:
product_form = ProductForm(request.POST, request.session['database'])
Which gives me an error:
init() takes 2 positional arguments but 3 were given
I know that something is not valid with my manner to override the __init__
method but i'm to noobish in python to realize that.
EDIT 3
Last errors:
Traceback:
File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\env\lib\site-packages\django\core\handlers\base.py" in get_response 149. response = self.process_exception_by_middleware(e, request)
File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\env\lib\site-packages\django\core\handlers\base.py" in get_response 147. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\gims\apps\inventory\views.py" in create 247. if product_form.is_valid():
File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\env\lib\site-packages\django\forms\forms.py" in is_valid 161. return self.is_bound and not self.errors
File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\env\lib\site-packages\django\forms\forms.py" in errors 153. self.full_clean()
File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\env\lib\site-packages\django\forms\forms.py" in full_clean 362. self._clean_fields()
File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\env\lib\site-packages\django\forms\forms.py" in _clean_fields 374. value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
File "C:\Users\Pavarine\Documents\Visual Studio 2015\Projects\gims\gims\env\lib\site-packages\django\forms\widgets.py" in value_from_datadict 231. return data.get(name)
Exception Type: AttributeError at /inventory/product/create Exception Value: 'tuple' object has no attribute 'get'
Upvotes: 3
Views: 1561
Reputation: 6341
To enable the router just add it to DATABASE_ROUTERS
in your setting.py
, see detailes here: https://docs.djangoproject.com/en/1.9/topics/db/multi-db/#using-routers
Every router method gets hints
dictionary, which should contain instance
key representing the model instance being used. Depending on what information you want to get from the view, you might be good with the information from instance attributes. See here: https://docs.djangoproject.com/en/1.9/topics/db/multi-db/#topics-db-multi-db-hints.
I.e.:
def db_for_read(self, model, **hints):
model_instance = hints.get('instance')
if model_instance is not None:
pass
#perform actions with model_instance attributes
return None
Other then this I don't think there's other straight way to pass information from the active view to the router, as routers are supposed to work with models and make decisions based on models being used.
DB routers are used for automatic DB selection in models layer, but model methods allow to select the DB manually. See examples here: https://docs.djangoproject.com/en/1.9/topics/db/multi-db/#manually-selecting-a-database
So the other solution is to redefine methods of your form class, i.e. save()
(see https://docs.djangoproject.com/en/1.9/topics/forms/modelforms/#the-save-method), and save the form data manually specifying the DB to use with using
argument. I.e.:
class ProductForm(ModelForm):
class Meta:
...
def save(self, commit=True):
product_to_save=super(ProductForm, self).save(commit=False)
#the above creates product_to_save instance, but doesn't saves it to DB
product_to_save.save(using=='banco1')
return product_to_save
In the comments I suggested to subclass the field that needs to be validated against the other DB, but there's probably even easier way... Since it's FK field it's probably the instance of ModelChoiceField
which accepts the queryset
argument in constructor, so you could provide it, i.e.:
class ProductForm(ModelForm):
category_field = forms.ModelMultipleChoiceField(queryset=Category.objects.using('banco1'))
...
See: https://docs.djangoproject.com/en/1.9/ref/forms/fields/#fields-which-handle-relationships
Answer to EDIT2
The way you redefined the constructor your database
argument gets request.POST
and request.session['database']
is not mapped to anything, that's why you get the error.
You should account for other arguments and also call superclass constructor or you'll break the MRO, so something like this should do the job:
class ProductForm(ModelForm):
...
def __init__(self, *args, db_to_use='default', **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
#category = forms.ModelChoiceField(queryset=Category.objects.using(db_to_use).all())
#the below line suggested as improvement by Alasdair and confirmed as working by Pavarine, so I updated the answer here
self.fields['category'].queryset = Category.objects.using(database).all()
and then as usual, but use named argument:
product_form = ProductForm(request.POST, db_to_use=request.session['database'])
Upvotes: 4