Django - Get object id in render_change_form (ModelAdmin) - django

I have these two models and modeladmin. When adding a new host in the list of available hostuser only appear hostusers that are not assigned to another host.
The issue is if I edit an already created host its actual hostuser id is also filtered so I want to do is to exclude hostuser id that is currently assigned.
How I can specify in the exclude the current id from the hostuser field?
The statement that I need is written between *
Thanks in advance
Models.py
class HostUser(models.Model):
name = models.CharField(max_length=200)
{..More Data..}
class Host(models.Model):
{..More Data..}
hostuser = models.ForeignKey(HostUser, blank=True, null=True)
Admin.py
class HostAdmin(admin.ModelAdmin):
{..More Data..}
def render_change_form(self, request, context, *args, **kwargs):
list_names = Host.objects.values_list('hostuser__id', flat=True).exclude(hostuser__id=None).exclude(hostuser__id=**ACTUAL HOSTUSER_ID**)
list_names = [int(ids) for ids in list_names]
context['adminform'].form.fields['hostuser'].queryset = HostUser.objects.exclude(id__in=list_names)
return super(HostAdmin, self).render_change_form(request, context, args, kwargs)

(Answered in a question edit. Converted to a community wiki answer. See What is the appropriate action when the answer to a question is added to the question itself? )
The OP wrote:
Solved using kwargs, the Modeladmin looks like this:
def render_change_form(self, request, context, *args, **kwargs):
try:
list_names = Host.objects.values_list('hostuser__id', flat=True).exclude(hostuser__id=None).exclude(hostuser__id=kwargs['obj'].hostuser.id)
except:
list_names = Host.objects.values_list('hostuser__id', flat=True).exclude(hostuser__id=None)
list_names = [int(ids) for ids in list_names]
context['adminform'].form.fields['hostuser'].queryset = HostUser.objects.exclude(id__in=list_names)
return super(HostAdmin, self).render_change_form(request, context, args, kwargs)

Related

How to access run-time request.session values in forms.py definition?

I have an inventory management app that will be serving multiple locations (called contexts in my app). When a user is logged in, their current context is stored as a value in request.sessions.
I would like users to only be able to browse and retrieve records for their own location.
I've been trying to this by filtering the queryset that is called in the form definition to populate the select dropdown, i.e.
referenced_catalog = forms.ModelChoiceField(
queryset=Inventory_unit_catalog.objects.all().filter(parent_business_unit_context_id=user_context_id),
I've tried implementing several different (but similar) approaches from various SO posts, that involve defining an init block to the form, such as:
class InventoryStockAddForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user_context_id = kwargs.pop('user_context_id', None)
super(InventoryStockAddForm, self).__init__(*args, **kwargs)
name = forms.CharField(max_length=96,widget=forms.TextInput(),required=True)
referenced_catalog = forms.ModelChoiceField(
queryset = Inventory_unit_catalog.objects.all().filter(parent_business_unit_context_id=self.user_context_id),
label = u"",
widget = ModelSelect2Widget(
model=Inventory_unit_catalog,
search_fields=['name__icontains'],
attrs={'data-placeholder': 'Select catalog...', 'data-width': '35em'},
required=False))
class Meta():
model = Inventory_unit_stock
fields = ('name',)
(Different SO answers had one way or the other.)
Then in views.py:
user_context_id = request.session.get('user_context_id')
...
add_form = InventoryStockAddForm(user_context_id=user_context_id)
I've even tried using the SessionStore per https://djangobook.com/using-sessions-views-2/:
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
s = SessionStore()
user_context_id = s['user_context_id']
but it always fails at the moment the forms.py is updated as Django validates the code and cannot find a key value at the moment of validation.
Any advice would be appreciated, thanks!
You can't access self.user_context_id inside referenced_catalog = forms.ModelChoiceField(...) - that code runs when the module is loaded, not when the form is initialised.
Instead, you should set the queryset inside the __init__ method.
class InventoryStockAddForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user_context_id = kwargs.pop('user_context_id', None)
super(InventoryStockAddForm, self).__init__(*args, **kwargs)
self.fields['referenced_catalog'].queryset = Inventory_unit_catalog.objects.all().filter(parent_business_unit_context_id=self.user_context_id)
referenced_catalog = forms.ModelChoiceField(
queryset = Inventory_unit_catalog.objects.none(),
label = u"",
widget = ModelSelect2Widget(
model=Inventory_unit_catalog,
search_fields=['name__icontains'],
attrs={'data-placeholder': 'Select catalog...', 'data-width': '35em'},
required=False))

How can I customize default to django REST serializer

http://www.django-rest-framework.org/api-guide/validators/#currentuserdefault
I wants to read default value from userprofile automatically. Right now the offical method support User and DateTime. But I want my customized value. How can I do that?
owner = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
This is my workaround. Copy the example from source code and place it here.
Hope near future it has friendly solution.
class CurrentBranchDefault:
def set_context(self, serializer_field):
self.user = serializer_field.context['request'].user
self.branch = self.user.userprofile.selected_branch
def __call__(self):
return self.branch
def __repr__(self):
return unicode_to_repr('%s()' % self.__class__.__name__)
class StaffOrderSerializer(serializers.ModelSerializer):
branch = serializers.HiddenField(default=CurrentBranchDefault())
If you want to calculate one hidden field, using other incoming fields in serializer,
than you need to use serializer_field.context['request'].data
This "data" will be validated before "set_context()", so you can use it in safe.
I hope it will help someone else.
class DefineNoteType:
def set_context(self, serializer_field):
# setting field "type", calculated by other serializer fields
data = serializer_field.context['request'].data
subscriber = data.get('subscriber', None)
connection = data.get('connection', None)
if subscriber:
self.type = 'subscriber_type'
elif connection:
self.type = 'connection_type'
else:
raise serializers.ValidationError('Custom error.')
def __call__(self):
return self.type
class NoteSerializer(serializers.ModelSerializer):
type = serializers.HiddenField(default=DefineNoteType())

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

How can I change the returned value of a field in a QuerySet based on some passed parameter?

I want to be able to return different values from a Queryset (not changing the database) based on a parameter. I'd like to be able to write it into the model so that it is enforced everywhere.
If the user is a certain type of user, I would want a QuerySet field to be blank (or "hidden" or something similar).
Here's a simplified Model:
class SomeDetails(models.Model):
size = models.FloatField()
this_is_okay_to_show = models.TextField()
not_always_ok = models.TextField()
Simplified Queryset:
qsSomeDetails = SomeDetails.objects.all()
I want not_always_ok to either return the text value stored in the database or return an empty string (or 'hidden' or similar).
Template Filter would work, but it really needs to be in the model.
I'm not sure how to pass a parameter through to make it work.
I feel like the answer is right in front of me, but I'm just not seeing it.
define a Manager class for SomeDetails model:
class SomeDetailsManager(models.Manager):
def __getattr__(self, attr, *args):
try:
return getattr(self.__class__, attr, *args)
except AttributeError:
return getattr(self.get_query_set(), attr, *args)
def get_query_set(self):
return self.model.QuerySet(self.model)
And change your SomeDetails like this:
from django.db.models.query import QuerySet
class SomeDetails(models.Model):
size = models.FloatField()
this_is_okay_to_show = models.TextField()
not_always_ok = models.TextField()
objects = SomeDetailsManager()
class QuerySet(QuerySet):
def get_user(self, user, *args, **kwargs):
_objects = self.filter(*args, **kwargs)
for obj in _objects:
if user.username = 'foo': #change with your condition
obj.not_always_ok = ''
return _objects
Now you can use
qsSomeDetails = SomeDetails.objects.filter(pk=1).get_new_objects(request.user)
note:
first done all your filter and use get_new_objects(request.user) as last part.

Django AdminForm field default value

I have a Django admin form.
And now I want to fill it's initial field with data based on my model. So I tried this:
class OrderForm(forms.ModelForm):
class Meta:
model = Order
email = CharField(initial="null", widget=Textarea(attrs={'rows': 30, 'cols': 100}))
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
products = kwargs['instance'].products.all()
self.message = purchase_message % (
"".join(["<li>" + p.name + ": " + str(p.price) + "</li>" for p in products]),
reduce(lambda x, y:x + y.price, products, 0)
)
# and here I have a message in self.message variable
super(OrderForm, self).__init__(*args, **kwargs)
At this point i don't know how to access email field to set it's initial value before widget is rendered. How can i do this?
Assuming the value is based on 'request' you should use this:
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['my_field_name'].initial = 'abcd'
return form
Since Django 1.7 there is a function get_changeform_initial_data in ModelAdmin that sets initial form values:
def get_changeform_initial_data(self, request):
return {'name': 'custom_initial_value'}
EDIT: Apart from that, #Paul Kenjora's answer applies anyway, which might be useful if you already override get_form.
In case of inline (InlineModelAdmin) there is no get_changeform_initial_data. You can override get_formset and set formset.form.base_fields['my_field_name'].initial.
I'm not too sure what you need to set email to, but You can set the initial values in lots of different places.
Your function def init() isn't indented correctly which i guess is a typo? Also, why are you specifically giving the email form field a TextInput? It already renders this widget by default
You can set the email's initial value in your form's initialized (def __ init __(self))
(self.fields['email'].widget).initial_value = "something"
or in the model.py
email = models.CharField(default="something")
or as you have in forms.py
email = models.CharField(initial="something")
I needed the first solution of pastylegs since the other ones overwrite the whole Widget including, for example, the help text. However, it didn't work for me as he posted it. Instead, I had to do this:
self.fields['email'].initial = 'something'
In my case, I was trying to do a personalized auto-increment(based on current data and not a simple default) in a field of a django admin form.
This code is worked for me (Django 1.11):
from django import forms
class MyAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.initial['field_name'] = 'initial_value'