RelatedObjectDoesNotExist with TabularInline and proxy models - django

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]

Related

How can I filter the queryset I use in my ModelChoiceField field?

I have looked at several answers on SO but I haven't found one that address my particular case.
I have this ModelForm which I am using in Django admin panel.
class AssignInventoryForm(ModelForm):
class Meta:
model = Driver
fields = []
template = forms.ModelChoiceField(queryset=ItemTemplate.objects.all(),
empty_label="(Select Template)")
template.label = 'Template'
def save(self, driver):
driver = self.form_action(driver)
return driver
I would like to be able to filter ItemTemplate like so:
ItemTemplate.objects.filter(id=driver.item.id)
But obviously driver is not defined at that point of the code.
I found a good answer from here: https://simpleisbetterthancomplex.com/questions/2017/03/22/how-to-dynamically-filter-modelchoices-queryset-in-a-modelform.html
Here's what I had to change in my code.
In my admin class, DriverAdmin my proccess_action method now starts out like this:
def process_action(self, request, driver_uid, action_form, action_title):
driver = self.get_object(request, driver_uid)
if request.method != 'POST':
# Added drive as parameter to action_form.
form = action_form(driver)
else:
# Added drive as parameter to action_form BEFORE request.POST
form = action_form(driver, request.POST)
... rest of method unchanged ...
In my AssignInventoryForm() class I added this __init__() method.
def __init__(self, driver, *args, **kwargs):
super(AssignInventoryForm, self).__init__(*args, **kwargs)
self.fields['template'].queryset = ItemTemplate.objects.filter(id=driver.item.id)

Django - calling custom update behaviour when overriding its behavour

What I am trying to do is best described by example. Consider the following:
class QuerySetManager(models.Manager):
def get_queryset(self):
result = self.model.QuerySet(self.model)
try:
result = result.filter(is_deleted=False)
except FieldError:
pass
return result
class MyModel(model.Models):
# core fields
objects = QuerySetManager()
class Meta:
managed = False
db_table = 'my_model'
class QuerySet(QuerySet):
def update(self, *args, **kwargs):
if something_special:
# handle that special case
else:
# call custom update
In effect, I am trying to override the update method of QuerySet's superclass. If something special happens, I would like to implement update process myself, and otherwise - call the standard update method of the superclass.
Any help on what the correct syntax is?
UPDATE
Let me provide a bit detailed background.
I am using the pattern found here.
The entire architecture looks like this:
class DeleteMixin(models.Model):
is_deleted = models.BooleanField(default=False)
class Meta:
abstract = True
class QuerySetManager(models.Manager):
def get_queryset(self):
result = self.model.QuerySet(self.model)
try:
result = result.filter(is_deleted=False)
except FieldError:
pass
return result
class Sms(DeleteMixin):
# core fields
objects = QuerySetManager()
class Meta:
managed = False
db_table = 'sms'
class QuerySet(QuerySet):
def inbox(self, user):
return self.filter(sms_type_id = 1)
def outbox(self, user):
return self.filter(sms_type_id = 2)
def update(self, *args, **kwargs):
if something_special:
# handle that special case
else:
# the issue in question - call custom update
This architecture enables me to:
exclude records with is_deleted field equal to 1 (for those tables where there is such field)
use chainable filters like sms.objects.inbox().outbox()
It's not enitrely clear what you are asking. IF you are trying to add a custom update method to a Queryset's superclass update method like this:
class MyQuerySet(QuerySet):
def update(self, *args, **kwargs):
....
super(MyQuerySet,self).update(*args,**kwargs)
if you are trying to override the save method in the model
class MyModel(model.Models):
def save(self,*args,**kwargs):
...
super(MyModel,self).save(*args,**kwargs)
Update (pun intended)
The code sample I have posted above is correct. The QuerySet class definitely as an update method. as can be seen here:
https://github.com/django/django/blob/master/django/db/models/query.py#L630
And if you have sub classed QuerySet correctly you will not get the error. But you haven't
class QuerySet(QuerySet):
This is incorrect.
Eventually, I got it work this way:
def update(self, *args, **kwargs):
if something_special:
# handle that special case
else:
super(self.model.QuerySet, self).update(*args,**kwargs)

Django: Accessing parent object in new linline object

I have been combing through the internet for quite some while without finding any solution to this problem.
What I am trying to do...
I have the following models:
class TrackingEventType(models.Model):
required_previous_event = models.ForeignKey(TrackingEventType)
class TrackingEvent(models.Model):
tracking = models.ForeignKey(Tracking)
class Tracking(models.Model):
last_event = models.ForeignKey(TrackingEvent)
Now the main model is Tracking, so my admin for Tracking looks like this:
class TrackingEventInline(admin.TabularInline):
model = TrackingEvent
extra = 0
class TrackingAdmin(admin.ModelAdmin):
inlines = [TrackingEventInline]
That's it for the current setup.
Now my quest:
In the TrackingAdmin, when I add new TrackingEvent inlines, I want to limit the options of TrackingEventType to onlye those, that are allowed to follow on the last TrackingEvent of the Tracking. (Tracking.last_event == TrackingEventType.required_previous_event).
For this, I would need to be able to access the related Tracking on the InlineTrackingEvent, to access the last_event and filter the options for TrackingEventType accordingly.
So I found this: Accessing parent model instance from modelform of admin inline, but when I set up TrackingEventInline accordingly:
class MyFormSet(forms.BaseInlineFormSet):
def _construct_form(self, i, **kwargs):
kwargs['parent_object'] = self.instance
print self.instance
return super(MyFormSet, self)._construct_form(i, **kwargs)
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
print kwargs
self.parent_object = kwargs.pop('parent_object')
super(MyForm, self).__init__(*args, **kwargs)
class TrackingEventInline(admin.TabularInline):
form = MyForm
formset = MyFormSet
model = TrackingEvent
extra = 0
I get a KeyError at /admin/.../tracking/2/change/ 'parent_object' from self.parent_object = kwargs.pop('parent_object')
Does anyone know how to solve this? Am I approaching the problem the wrong way? I guess this would be pretty easy in a custom form in the frontend, but I really want to use the admin, because the whole application is built to be used from the admin, and it would be a hell lot of work to build a custom admin interface just because of this problem :)
Ok, so posting on StackOverflow is always helping to get the problem straight. I was able to put together a solution that works for me.
It includes defining my own Form in a outer function, as well as defining two InlineAdmin objects for TrackingEvent (one for update / edit, one just for insert).
Here's the code:
def create_trackingevent_form(tracking):
"""
"""
class TrackingEventForm(forms.ModelForm):
"""
Form for Tracking Event Inline
"""
def clean(self):
"""
May not be needed anymore, since event type choices are limited when creating new event.
"""
next_eventtype = self.cleaned_data['event_type']
tracking = self.cleaned_data['tracking']
# get last event, this also ensures last_event gets updated everytime the change form for TrackingEvent is loaded
last_eventtype = tracking.set_last_event()
if last_eventtype:
last_eventtype = last_eventtype.event_type
pk = self.instance.pk
insert = pk == None
# check if the event is updated or newly created
if insert:
if next_eventtype.required_previous_event == last_eventtype:
pass
else:
raise forms.ValidationError('"{}" requires "{}" as last event, "{}" found. Possible next events: {}'.format(
next_eventtype,
next_eventtype.required_previous_event,
last_eventtype,
'"%s" ' % ', '.join(map(str, [x.name for x in tracking.next_tracking_eventtype_options()]))
)
)
else:
pass
return self.cleaned_data
def __init__(self, *args, **kwargs):
# You can use the outer function's 'tracking' here
self.parent_object = tracking
super(TrackingEventForm, self).__init__(*args, **kwargs)
self.fields['event_type'].queryset = tracking.next_tracking_eventtype_options()
#self.fields['event_type'].limit_choices_to = tracking.next_tracking_eventtype_options()
return TrackingEventForm
class TrackingEventInline(admin.TabularInline):
#form = MyForm
#formset = MyFormSet
model = TrackingEvent
extra = 0
#readonly_fields = ['datetime', 'event_type', 'note']
def has_add_permission(self, request):
return False
class AddTrackingEventInline(admin.TabularInline):
model = TrackingEvent
extra = 0
def has_change_permission(self, request, obj=None):
return False
def queryset(self, request):
return super(AddTrackingEventInline, self).queryset(request).none()
def get_formset(self, request, obj=None, **kwargs):
if obj:
self.form = create_trackingevent_form(obj)
return super(AddTrackingEventInline, self).get_formset(request, obj, **kwargs)
I hope this helps other people with the same problem.. Some credit to the Stack Overflow threads that helped me come up with this:
Prepopulating inlines based on the parent model in the Django Admin
Limit foreign key choices in select in an inline form in admin
https://docs.djangoproject.com/en/1.9/ref/models/instances/#django.db.models.Model.clean_fields
Please do not hesitate to ask questions if you have any

Django field is blank in admin, but not on regular site

I have the following two forms:
class ProfileUser(SimpleProfileUser):
photo = PhotoField(verbose_name=_('photo'))
class Client(ProfileUser):
can_be_seen = models.BooleanField(_('can be seen by other members'), default=True)
def __init__(self, *args, **kwargs):
Client._meta.get_field('phone').blank = True
What I basically have is two type of members.
I've made common class profile, where photo is not-null.
However, in Client class I want to accept empty values of photo.
This works perfectly in admin, but doesn't work on the regular site.
How can I fix that?
Code for form is:
class CommonClientForm(forms.ModelForm):
"""This form for gathering common features in both admin and member forms
"""
class Meta:
model = Client
fields = '__all__'
full_location = MarkerLocationField(*[Meta.model._meta.get_field(key)
for key in ['latitude', 'longitude', 'location']], label=_('Location'))
def __init__(self, *args, **kwargs):
individual_attrs = kwargs.pop('individual_attrs', None)
super(CommonClientForm, self).__init__(*args, **kwargs)
self.fields['birthday'].widget = SelectDateWidget(individual_attrs=individual_attrs)
if self.instance:
self.initial['full_location'] = [self.instance.latitude, self.instance.longitude, self.instance.location]
If I wrote Client._meta.get_field('phone').blank = True after the class definition (outside it), then it works.
I think the reason is that on client side fields are initialized before calling init, while in the admin after calling init.

Django ManyToMany field not preselecting rows with custom manager

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