Django 1.2.5: I've got a model with a custom manager. Data is saved correctly, but it's not retrieved correctly for related objects.
My models are:
Question -> related to a SubjectiveStatistic
SubjectiveStatistic extends Statistic as a Proxy. It has a custom manager to restrict the result set to only where the 'type' field matches 'SubjectiveStatistic' (the type field contains the class name of the object).
Here's the Question:
class Question(models.Model):
subjective_statistic = models.ManyToManyField(SubjectiveStatistic, null=True, blank=True)
Here is the SubjectiveStatistic:
class SubjectiveStatistic(Statistic):
## Use a custom model manager so that the default object collection is
# filtered by the object class name.
objects = RestrictByTypeManager('SubjectiveStatistic')
## Override the __init__ method to set the type field
def __init__(self, *args, **kwargs):
self.type = self.__class__.__name__
return super(SubjectiveStatistic, self).__init__(*args, **kwargs)
class Meta:
proxy = True
Here is the manager:
from django.db import models
## Custom model manager that returns objects filtered so that 'type' == a
# given string.
class RestrictByTypeManager(models.Manager):
def __init__(self, type='', *args, **kwargs):
self.type = type
return super(RestrictByTypeManager, self).__init__(*args, **kwargs)
def get_query_set(self):
return super(RestrictByTypeManager, self).get_query_set().filter(type=self.type)
What do I need to do so that related objects are returned correctly? question.subjective_statistic.exists() doesn't return anything, despite relations existing in the database.
Perhaps it's because the RestrictByTypeManager extends Manager instead of ManyRelatedManager (but I can't because that's an inner class) or something like that?
To use your custom manager from the Question model, add use_for_related_fields = True in the definition of the custom manager:
from django.db import models
## Custom model manager that returns objects filtered so that 'type' == a
# given string.
class RestrictByTypeManager(models.Manager):
use_for_related_fields = True
def __init__(self, type='', *args, **kwargs):
self.type = type
return super(RestrictByTypeManager, self).__init__(*args, **kwargs)
def get_query_set(self):
return super(RestrictByTypeManager, self).get_query_set().filter(type=self.type)
This way, RestrictByTypeManager will be used as manager for SubjectiveStatistic models either directly or in reverse access like with manytomany relations.
More informations here: Controlling automatic Manager types
Related
My app's models include the Service model along with three proxy models:
class Service(models.Model):
SERVICE_TYPE_CHOICES = [
('AR', 'Area'),
('OC', 'Occupancy'),
('CO', 'Consumption'),
]
service_type = models.CharField(max_length=2, choices=SERVICE_TYPE_CHOICES)
building = models.ForeignKey(Building, on_delete=models.CASCADE)
def __init__(self, *args, **kwargs):
super(HouseService, self).__init__(*args, **kwargs)
subclasses = {
'AR' : AreaService,
'OC' : OccupancyService,
'CO' : ConsumptionService,
}
self.__class__ = subclasses[self.service_type]
class AreaServiceManager(models.Manager):
def get_queryset(self):
return super(AreaServiceManager, self).get_queryset().filter(service_type='AR')
def create(self, **kwargs):
kwargs.update({'service_type': 'AR'})
return super(AreaServiceManager, self).create(**kwargs)
class AreaService(Service):
objects = AreaServiceManager()
def save(self, *args, **kwargs):
self.service_type = 'AR'
return super(AreaService, self).save(*args, **kwargs)
class Meta:
proxy = True
# Manager and Model class definitions omitted for the other two proxy models
This works as intended such that I can create instances for each proxy model transparently by registering them for the admin interface, and queries for e.g. AreaService.objects returns the proper subset of Service instances, and Service.objects.all() returns a list of instances not for the Service model, but for its subclasses.
But when I try to add a TabularInline to admin.py –
class ServiceInlineAdmin(admin.TabularInline):
model = Service
extra = 0
class BuildingAdmin(admin.ModelAdmin):
inlines = [ServiceInlineAdmin,]
– I get the following error message for the line self.__class__ = subclasses[self.service_type] in models.py (s. above):
__class__
<class 'services.models.Service'>
args
()
kwargs
{}
self
Error in formatting: RelatedObjectDoesNotExist: Service has no building.
subclasses
{'AR': <class 'services.models.AreaService'>,
'CO': <class 'services.models.ConsumptionService'>,
'OC': <class 'services.models.OccupancyService'>}
I retrieved all objects via the Django shell, and checked my database: All Service instances/DB entries are linked to a building. Commenting out the custom __init__ function solves the issue, and all linked services are displayed for each building (but I obviously lose the ability to work with the subclasses as required).
Any pointers to the cause of this error are greatly appreciated.
Problem solved. It turned out that the TabularInline form created 4 additional empty Service instances (regardless of extra = 0). To circumvent this issue, I modified my custom __init__ function like this:
def __init__(self, *args, **kwargs):
super(Service, self).__init__(*args, **kwargs)
subclasses = {
'AR' : AreaService,
'OC' : OccupancyService,
'CO' : ConsumptionService,
}
if self.id:
self.__class__ = subclasses[self.service_type]
In django 4.1.3
Trying to filter a ForeignKey field query using another selected ForeignKey field value in a ModelForm to limit the filter depending on the selected exhibitors corresponding id.
from django import forms
from .models import Entry, Exhibitor, Pen
class EntryForm(forms.ModelForm):
class Meta:
fields = ('exhibitor', 'pen', 'class_id',)
def __init__(self, *args, **kwargs):
super(EntryForm, self).__init__(*args, **kwargs)
# Can't figure out how to filter
# Using the selected exhibitor field value
self.fields['pen'].queryset = Pen.objects.filter(current_owner_id=1)
# where 1 should be the exhibitor_id
# using an int value for the current_owner_id works to filter the query
# but don't know how to access selected field values, or would this work in a pre_save function?
# tried
self.fields['pen'].queryset = Pen.objects.filter(current_owner_id=self.fields['pen'])
TypeError at /admin/exhibitors/entry/add/ Field 'id' expected a number but got <django.forms.models.ModelChoiceField object at 0x2b630a6b2100>.
You have a ModelForm. It means, your form has always an instance of Entry.
in this case you can do:
Use instance attribute:
class EntryForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
... # any staff
exibitor = self.instance.exibitor
... # any staff
Use boundedfield.value method:
class EntryForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
... # any staff
exibitor = self['exibitor'].value()
... # any staff
You can get 'exibitor' before and send it in form.__init__:
formentry = EntryForm(.... initial = { ..., 'exibitor': something })
The best way - to change Pen.Queryset in formfield_callback method in ModelForm Factory.
More here: https://docs.djangoproject.com/en/4.1/ref/forms/models/#modelform-factory
My code for custom manager below:
class CustomManager(models.Manager):
def create(self, **kwargs):
obj, created = Group.objects.get_or_create(name='group1')
kwargs.update({'groups': obj})
return super(CustomManager, self).create(**kwargs)
which creates TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use groups.set() instead..
As is specified, you can not directly pass a groups= in a create. You can use .set(…) [Django-doc] (or .add(…) [Django-doc]) for example, so:
class CustomManager(models.Manager):
def create(self, **kwargs):
object = super(CustomManager, self).create(**kwargs)
group, __ = Group.objects.get_or_create(name='group1')
object.groups.set([group])
return object
We have one application containing models.py which contains n no. of classes that inherits base class.We want to create form which dynamically takes value from user n saves in db but problem is that we want to use django form fields instead of django model forms.
As we know there are some fields missing in django forms such as PositiveIntegerField, CommaSeparetedIntegerFields etc. How can we achieve this using django form fields?
If we write follwing code in shell.
from djnago.db import models
mvar = models.PositiveIntegerFields()
from django import forms
fvar = forms.PositiveIntegerFields()
AttributeError: 'module' object has no attribute 'PositiveIntegerField'
forms.py
from django import forms
class ContextForm(forms.Form):
def __init__(self, rdict, *args, **kwargs):
super(ContextForm, self).__init__(*args, **kwargs)
for key in rdict.keys():
self.fields['%s' % str(key)] = getattr(forms,rdict.get(key))()
rdict = {'address': 'CharField','phone': 'CharField', 'Salary': 'PositiveIntegerField','first name': 'CharField','last name':'CharField'}
Looking at the source, all the field does is call the default form field with a keyword argument: min_value.
class PositiveIntegerField(IntegerField):
description = _("Positive integer")
def get_internal_type(self):
return "PositiveIntegerField"
def formfield(self, **kwargs):
defaults = {'min_value': 0}
defaults.update(kwargs)
return super(PositiveIntegerField, self).formfield(**defaults)
Therefore what you are looking for is merely
from django import forms
fvar = forms.IntegerField(min_value=0)
fvar.clean(-1)
# ValidationError: [u'Ensure this value is greater than or equal to 0.']
As for CommaSeparatedIntegerField, it looks like a CharField with some django.core.validators.validate_comma_separated_integer_list passed in.
f = forms.CharField(validators=[django.core.validators.validate_comma_separated_integer_list])
f.clean('1,2,3')
All this does is make sure the passed in string is '^[\d,]+$'. The field doesn't even do any python conversions... it doesn't really seem to save much time if just validates form input. Indeed, there's a comment that says "maybe move to contrib". Agreed..
Decided to look into this for fun. Here's a ModelForm generator that overrides model fields with new fields... It doesn't yet handle kwargs. It was just the first method I could think of to do this.. without looking into modelform generation itself. It constructs a regular ModelForm that modifies the form /after/ initialization.
MODEL_FIELD_MAP = {
models.IntegerField: forms.CharField,
# change all IntegerField to forms.CharField
}
def modelform_generator(mymodel):
class MyModelForm(forms.ModelForm):
class Meta:
model = mymodel
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
for name, form_field in self.fields.items():
try:
model_field = self._meta.model._meta.get_field_by_name(name)[0]
# is this a model field?
field_override = MODEL_FIELD_MAP.get(model_field.__class__)
# do we have this model field mapped to a form field?
if field_override:
self.fields[name] = field_override()
# set the form field to the target field class
except models.FieldDoesNotExist:
pass
return MyModelForm
I've overridden the default manager of my models in order to show only allowed items, according to the logged user (a sort of object-specific permission):
class User_manager(models.Manager):
def get_query_set(self):
""" Filter results according to logged user """
#Compose a filter dictionary with current user (stored in a middleware method)
user_filter = middleware.get_user_filter()
return super(User_manager, self).get_query_set().filter(**user_filter)
class Foo(models.Model):
objects = User_manager()
...
In this way, whenever I use Foo.objects, the current user is retrieved and a filter is applied to default queryset in order to show allowed records only.
Then, I have a model with a ForeignKey to Foo:
class Bar(models.Model):
foo = models.ForeignKey(Foo)
class BarForm(form.ModelForm):
class Meta:
model = Bar
When I compose BarForm I'm expecting to see only the filteres Foo instances but the filter is not applied. I think it is because the queryset is evaluated and cached on Django start-up, when no user is logged and no filter is applied.
Is there a method to make Django evalutate the ModelChoice queryset at run-time, without having to make it explicit in the form definition? (despite of all performance issues...)
EDIT
I've found where the queryset is evaluated (django\db\models\fields\related.py: 887):
def formfield(self, **kwargs):
db = kwargs.pop('using', None)
defaults = {
'form_class': forms.ModelChoiceField,
'queryset': self.rel.to._default_manager.using(db).complex_filter(self.rel.limit_choices_to),
'to_field_name': self.rel.field_name,
}
defaults.update(kwargs)
return super(ForeignKey, self).formfield(**defaults)
Any hint?
Had exactly this problem -- needed to populate select form with user objects from a group, but fun_vit's answer is incorrect (at least for django 1.5)
Firstly, you don't want to overwrite the field['somefield'].choices object -- it is a ModelChoiceIterator object, not a queryset. Secondly, a comment in django.forms.BaseForm warns you against overriding base_fields:
# The base_fields class attribute is the *class-wide* definition of
# fields. Because a particular *instance* of the class might want to
# alter self.fields, we create self.fields here by copying base_fields.
# Instances should always modify self.fields; they should not modify
# self.base_fields.
This worked for me (django 1.5):
class MyForm(ModelForm):
users = ModelMultipleChoiceField(queryset=User.objects.none())
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args,**kwargs)
site = Site.objects.get_current()
self.fields['users'].queryset = site.user_group.user_set.all()
class Meta:
model = MyModel
i use init of custom form:
class BT_Form(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(BT_Form, self).__init__(*args, **kwargs)
#prepare new values
cities = [(u'',u'------')] #default value
cities.extend([
(
c.pk,
c.__unicode__()
) for c in City.objects.filter(enabled=True).all()
])
self.fields['fly_from_city'].choices = cities #renew values
No way: I had to rewrite queryset definition (which is evaluated at startup)