Can't save a django form with this autocomplete - django

I'm using autocomplete-light and for some reason this specific class is not working--I can't see any major differences between it and the working autocompletes. My VirtualHost contains a fk to a Host provided that Host.contain_virtuals=True
Here's my form:
class VirtualHostForm(ServerForm):
def __init__(self, *args, **kwargs):
super(VirtualHostForm, self).__init__(*args, **kwargs)
self.helper.form_id = 'virtual_host_form'
host = forms.ModelChoiceField(Host.objects.all(),
widget=autocomplete_light.ChoiceWidget('HostAutocomplete'),
label='Associated Host'
class Meta:
model = Virtual
fields = ServerForm.Meta.fields + ['host',]
widgets = autocomplete_light.get_widgets_dict(Server)
I've tried two ways, each with their own errors:
class HostAutocomplete(autocomplete_light.AutocompleteBase):
#registers autocomplete for hosts that can contain virtuals
autocomplete_js_attributes = {'placeholder': 'Select a host'}
widget_template='assets/subtemplates/autocomplete_remove.html',
choice_template='assets/_autocomplete_choice.html',
def choices_for_request(self):
q = self.request.GET.get('q', '')
hosts = Host.objects.values_list('name', flat=True)
return hosts.filter(name__icontains=q, contain_virtuals=True).distinct()
autocomplete_light.register(HostAutocomplete)
This way, I get the error: 'NotImplementedType' object is not callable. That seemed to relate to not having a choices_for_values method (although some of my other Autocompletes don't) so I added:
def choices_for_values(self):
choices = Host.objects.filter(id__in=self.values)
return choices
(I don't really know what I'm doing here--I couldn't find much in the documentation, so I took my best guess).
That gave me a invalid literal for int() with base 10: which I guess means it's looking at the name, instead of the pk for a foreign key relationship? That's a guess.
It should be noted that all of the above attempts did not render the template-formatting correctly, but did at least give the correct options for the choices.
So finally I tried:
autocomplete_light.register(
Host,
autocomplete_light.AutocompleteModelTemplate,
name='HostAutocomplete',
widget_template='assets/subtemplates/autocomplete_remove.html',
choice_template='assets/_autocomplete_choice.html',
autocomplete_js_attributes={'placeholder': 'Type associated host'},
search_fields=['name'],
)
which saves (and contains the correct formatting) but does not filter the choices based on contain_virtuals=True; it just includes all possible hosts.
EDIT:
Thanks to #jpic's help below, this works:
class HostAutocomplete(autocomplete_light.AutocompleteModelTemplate):
#registers autocomplete for hosts that can contain virtuals
autocomplete_js_attributes = {'placeholder': 'Select a host'}
choice_template='assets/_autocomplete_choice.html',
def choices_for_request(self):
q = self.request.GET.get('q', '')
hosts = Host.objects.filter(contain_virtuals=True,name__icontains=q).distinct()
return hosts
def choices_for_values(self):
choices = Host.objects.filter(id__in=self.values)
return choices
autocomplete_light.register(Host, HostAutocomplete)

This is because you inherit from AutocompleteBase instead of AutocompleteModelBase ! You could use AutocompleteModelTemplate as well.
Check out how Autocomplete design is explained in docs for v2 (that part doesn't change from v1 to v2): http://django-autocomplete-light.readthedocs.org/en/v2/autocomplete.html

Related

Django - Custom values for inline list item field

After searching for a while, I can't seem to find any answer for this so I'm asking this question. What I want to is display some values based on a person's salary and a bracket. My models looks like this.
class Employee
salary = models.DecimalField
class Benefit
some_fields
class EmployeeBenefit
employee = models.ForeignKey
benefit = models.ForeignKey
class BenefitVersion
benefit = models.ForeignKey
class BenefitBracket
benefit_version = models.ForeignKey
from_salary = models.DecimalField
to_salary = models.DecimalField
the_fields_I_want_to_display
As you can see it's quite deep, there would be a bunch of querying to do to get to the fields I want.
I basically need to get the employee's salary(which is easy because this is gonna be inside EmployeeAdmin) then get the current benefit of the EmployeeBenefit list item, then based on the benefit and the employee's salary, get the bracket and then display some of it's fields on the inline.
I want to display the the_fields_I_want_to_display on the admin.TabularInline for EmployeeBenefit inside my EmployeeAdmin. I was testing using a forms.ModelForm in the inline and modifying it's contents using get_form based on this answer but django is not calling get_form. I also previously tried using calculated_fields but it's not being rendered as well.
I'm using django 3.1.7 btw
Edit:
I found this and try it out right now. I think it has potential but unfortunately the obj that get_formset gives is Employee so I still need to find a way to get the right EmployeeBenefit for the list item
After some more searching, I already figured out a way to do this. Based on this SO answer, I got an idea on how I should receive the data I passed to the formset using get_formset.
So, in my get_formset, I did something like this.
def get_formset(self, request, obj=None, **kwargs):
formset = super(Inline, self).get_formset(request, obj, **kwargs)
formset.request = request
benefit_details_dict_arr = []
emp_contribs = models.EmployeeBenefit.objects.filter(employee=obj)
#because what I'm getting here is Employee and not a EmployeeBenefit, I have to query it first
for contrib in emp_contribs:
bracket = contrib.get_benefit_bracket(datetime.now())
benefit_details_dict_arr.append({
"key": bracket.val,
"key2": bracket.val2,
})
formset.benefit_details = benefit_details_dict_arr
return formset
Then on the formset, I receive the data this way.
class EmpBenefitFormSet(forms.models.BaseInlineFormSet):
model = models.EmployeeBenefit
def __init__(self, *args, **kwargs):
super(EmpBenefitFormSet, self).__init__(*args, **kwargs)
cntr = 0
for form in self.forms:
init_data = self.benefit_details[cntr]
for field in form.fields:
if field in init_data:
form.initial[field] = init_data[field]
form.fields[field].required = False
form.fields[field].disabled = True
cntr += 1
Also, if you're gonna use this way of receiving the data, each field you want to inflate should be declared on the fields of the inline. This should work if the fields you want to set an initial value are actual fields of the model. You might need to tweak this a bit to work for custom fields

Loading choices into choicefield - strange behavior

I have a django form with a choicefield, where I dynamically load some choices into the field:
class EntryForm(forms.Form):
project = forms.ChoiceField()
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(EntryForm, self).__init__( *args, **kwargs)
CHOICES2=[]
for x in Project.objects.all() :
if user in x.users.all():
CHOICES2.append((x.name,x.name))
CHOICES1 = [(x.name,x.name) for x in Project.objects.all()]
print CHOICES2==CHOICES1 #this is True in this case
self.fields['project']=forms.ChoiceField(choices=CHOICES2)
The form is loaded into the template with {{form.as_table}}. The form does not show a dropdown for the project field.
Now the strange thing: if I change the last line to:
self.fields['project']=forms.ChoiceField(choices=CHOICES1)
it works, although the print statement of the "=="" comparison returns True (the lists are purposely the same - this is just for testing). I really have no idea how this can even work technically.
Edit - my project model:
class Project(BaseModel):
name = models.CharField(max_length=80)
users = models.ManyToManyField(User)
Your field named project already exists and there's no need to construct another one as you are doing. It's better to just set the choices on the existing field:
self.fields['project'].choices = CHOICES2
But maybe you'd be better off using a ModelChoiceField:
project = ModelChoiceField(queryset=Project.objects.none())
and then set the queryset you want in init like so:
self.fields['project'].queryset=Project.objects.filter(users__in=[user])
..which should give you a list of all projects associated with user.
I think you have to use queryset argument, which is mandatory:
https://docs.djangoproject.com/en/1.8/ref/forms/fields/#django.forms.ModelChoiceField.queryset
ChoiceField must be declared with (queryset=None), and in the __init__ method you complete the query:
https://docs.djangoproject.com/en/1.11/ref/forms/fields/#fields-which-handle-relationships
The problem could be about the execution order of the queries, or the cache of non-lazy queries.
And I agree with little_birdie: the field already exists.

autocomplete_light is not showing correct suggestions with choice_for_request

I have autocomplete-light in the django modal form. I wanted to apply dynamic filtering in the suggestion box, that's why I have used choice_for_request() in the autocompletebasemodel. But because of using choice_for_request(), the suggestions are not according to the keyword typed but all the values that can be entered.
This is my form:
class CamenuForm(autocomplete_light.ModelForm):
class Meta:
model = Ca_dispensaries_item
exclude = ('dispensary',)
autocomplete_fields = ('item',)
def __init__(self, *args, **kwargs):
self.category = kwargs.pop('category', None)
super(CamenuForm, self).__init__(*args, **kwargs)
self.fields['item'].queryset=Items.objects.filter(product_type__name=self.category)
This is the registry and the class:
autocomplete_light.register(Items, AutoComplete )
class:
class AutoComplete(autocomplete_light.AutocompleteModelBase):
search_fields=('item_name',)
def choices_for_request(self):
category = self.request.session.get('category','')
if category:
choices = Items.objects.filter(product_type__name=category)
return self.order_choices(choices)[0:self.limit_choices]
I really dont know what changes to make in changes_for_request so as to make it work correctly
After going through various documents, the solution which worked as correctly as it should be is
def choices_for_request(self):
category = self.request.session.get('category','')
item=self.request.GET.get('q','')
choices = self.choices.all()
if item:
choices = choices.filter(item_name__icontains=item)
super(AutoComplete, self).choices_for_request()
if category:
choices = choices.filter(product_type__name=category)
return self.order_choices(choices)[0:self.limit_choices]
I missed out
item=self.request.GET.get('q','')
autocomplete-light uses the get method and the predefined literal q to transfer the value typed by user.
I wasn't able to crack out the meaning of q. After some hit and trial, I got that it is used to store the user given value in suggestion box.

DRF - How to get WritableField to not load entire database into memory?

I have a very large database (6 GB) that I would like to use Django-REST-Framework with. In particular, I have a model that has a ForeignKey relationship to the django.contrib.auth.models.User table (not so big) and a Foreign Key to a BIG table (lets call it Products). The model can be seen below:
class ShoppingBag(models.Model):
user = models.ForeignKey('auth.User', related_name='+')
product = models.ForeignKey('myapp.Product', related_name='+')
quantity = models.SmallIntegerField(default=1)
Again, there are 6GB of Products.
The serializer is as follows:
class ShoppingBagSerializer(serializers.ModelSerializer):
product = serializers.RelatedField(many=False)
user = serializers.RelatedField(many=False)
class Meta:
model = ShoppingBag
fields = ('product', 'user', 'quantity')
So far this is great- I can do a GET on the list and individual shopping bags, and everything is fine. For reference the queries (using a query logger) look something like this:
SELECT * FROM myapp_product WHERE product_id=1254
SELECT * FROM auth_user WHERE user_id=12
SELECT * FROM myapp_product WHERE product_id=1404
SELECT * FROM auth_user WHERE user_id=12
...
For as many shopping bags are getting returned.
But I would like to be able to POST to create new shopping bags, but serializers.RelatedField is read-only. Let's make it read-write:
class ShoppingBagSerializer(serializers.ModelSerializer):
product = serializers.PrimaryKeyRelatedField(many=False)
user = serializers.PrimaryKeyRelatedField(many=False)
...
Now things get bad... GET requests to the list action take > 5 minutes and I noticed that my server's memory jumps up to ~6GB; why?! Well, back to the SQL queries and now I see:
SELECT * FROM myapp_products;
SELECT * FROM auth_user;
Ok, so that's not good. Clearly we're doing "prefetch related" or "select_related" or something like that in order to get access to all the products; but this table is HUGE.
Further inspection reveals where this happens on Line 68 of relations.py in DRF:
def initialize(self, parent, field_name):
super(RelatedField, self).initialize(parent, field_name)
if self.queryset is None and not self.read_only:
manager = getattr(self.parent.opts.model, self.source or field_name)
if hasattr(manager, 'related'): # Forward
self.queryset = manager.related.model._default_manager.all()
else: # Reverse
self.queryset = manager.field.rel.to._default_manager.all()
If not readonly, self.queryset = ALL!!
So, I'm pretty sure that this is where my problem is; and I need to say, don't select_related here, but I'm not 100% if this is the issue or where to deal with this. It seems like all should be memory safe with pagination, but this is simply not the case. I'd appreciate any advice.
In the end, we had to simply create our own PrimaryKeyRelatedField class to override the default behavior in Django-Rest-Framework. Basically we ensured that the queryset was None until we wanted to lookup the object, then we performed the lookup. This was extremely annoying, and I hope the Django-Rest-Framework guys take note of this!
Our final solution:
class ProductField(serializers.PrimaryKeyRelatedField):
many = False
def __init__(self, *args, **kwargs):
kwarsgs['queryset'] = Product.objects.none() # Hack to ensure ALL products are not loaded
super(ProductField, self).__init__(*args, **kwargs)
def field_to_native(self, obj, field_name):
return unicode(obj)
def from_native(self, data):
"""
Perform query lookup here.
"""
try:
return Product.objects.get(pk=data)
except Product.ObjectDoesNotExist:
msg = self.error_messages['does_not_exist'] % smart_text(data)
raise ValidationError(msg)
except (TypeError, ValueError):
msg = self.error_messages['incorrect_type'] % type(data)
raise ValidationError(msg)
And then our serializer is as follows:
class ShoppingBagSerializer(serializers.ModelSerializer):
product = ProductField()
...
This hack ensures the entire database isn't loaded into memory, but rather performs one-off selects based on the data. It's not as efficient computationally, but it also doesn't blast our server with 5 second database queries loaded into memory!

Django: Get list of model fields?

I've defined a User class which (ultimately) inherits from models.Model. I want to get a list of all the fields defined for this model. For example, phone_number = CharField(max_length=20). Basically, I want to retrieve anything that inherits from the Field class.
I thought I'd be able to retrieve these by taking advantage of inspect.getmembers(model), but the list it returns doesn't contain any of these fields. It looks like Django has already gotten a hold of the class and added all its magic attributes and stripped out what's actually been defined. So... how can I get these fields? They probably have a function for retrieving them for their own internal purposes?
Django versions 1.8 and later:
You should use get_fields():
[f.name for f in MyModel._meta.get_fields()]
The get_all_field_names() method is deprecated starting from Django
1.8 and will be removed in 1.10.
The documentation page linked above provides a fully backwards-compatible implementation of get_all_field_names(), but for most purposes the previous example should work just fine.
Django versions before 1.8:
model._meta.get_all_field_names()
That should do the trick.
That requires an actual model instance. If all you have is a subclass of django.db.models.Model, then you should call myproject.myapp.models.MyModel._meta.get_all_field_names()
As most of answers are outdated I'll try to update you on Django 2.2
Here posts- your app (posts, blog, shop, etc.)
1) From model link: https://docs.djangoproject.com/en/stable/ref/models/meta/
from posts.model import BlogPost
all_fields = BlogPost._meta.fields
#or
all_fields = BlogPost._meta.get_fields()
Note that:
all_fields=BlogPost._meta.get_fields()
Will also get some relationships, which, for ex: you can not display in a view.
As in my case:
Organisation._meta.fields
(<django.db.models.fields.AutoField: id>, <django.db.models.fields.DateField: created>...
and
Organisation._meta.get_fields()
(<ManyToOneRel: crm.activity>, <django.db.models.fields.AutoField: id>, <django.db.models.fields.DateField: created>...
2) From instance
from posts.model import BlogPost
bp = BlogPost()
all_fields = bp._meta.fields
3) From parent model
Let's suppose that we have Post as the parent model and you want to see all the fields in a list, and have the parent fields to be read-only in Edit mode.
from django.contrib import admin
from posts.model import BlogPost
#admin.register(BlogPost)
class BlogPost(admin.ModelAdmin):
all_fields = [f.name for f in Organisation._meta.fields]
parent_fields = BlogPost.get_deferred_fields(BlogPost)
list_display = all_fields
read_only = parent_fields
The get_all_related_fields() method mentioned herein has been deprecated in 1.8. From now on it's get_fields().
>> from django.contrib.auth.models import User
>> User._meta.get_fields()
I find adding this to django models quite helpful:
def __iter__(self):
for field_name in self._meta.get_all_field_names():
value = getattr(self, field_name, None)
yield (field_name, value)
This lets you do:
for field, val in object:
print field, val
This does the trick. I only test it in Django 1.7.
your_fields = YourModel._meta.local_fields
your_field_names = [f.name for f in your_fields]
Model._meta.local_fields does not contain many-to-many fields. You should get them using Model._meta.local_many_to_many.
It is not clear whether you have an instance of the class or the class itself and trying to retrieve the fields, but either way, consider the following code
Using an instance
instance = User.objects.get(username="foo")
instance.__dict__ # returns a dictionary with all fields and their values
instance.__dict__.keys() # returns a dictionary with all fields
list(instance.__dict__.keys()) # returns list with all fields
Using a class
User._meta.__dict__.get("fields") # returns the fields
# to get the field names consider looping over the fields and calling __str__()
for field in User._meta.__dict__.get("fields"):
field.__str__() # e.g. 'auth.User.id'
def __iter__(self):
field_names = [f.name for f in self._meta.fields]
for field_name in field_names:
value = getattr(self, field_name, None)
yield (field_name, value)
This worked for me in django==1.11.8
A detail not mentioned by others:
[f.name for f in MyModel._meta.get_fields()]
get, for example
['id', 'name', 'occupation']
and
[f.get_attname() for f in MyModel._meta.get_fields()]
get
['id', 'name', 'occupation_id']
If
reg = MyModel.objects.first()
then
reg.occupation
get, for example
<Occupation: Dev>
and
reg.occupation_id
get
1
MyModel._meta.get_all_field_names() was deprecated several versions back and removed in Django 1.10.
Here's the backwards-compatible suggestion from the docs:
from itertools import chain
list(set(chain.from_iterable(
(field.name, field.attname) if hasattr(field, 'attname') else (field.name,)
for field in MyModel._meta.get_fields()
# For complete backwards compatibility, you may want to exclude
# GenericForeignKey from the results.
if not (field.many_to_one and field.related_model is None)
)))
Just to add, I am using self object, this worked for me:
[f.name for f in self.model._meta.get_fields()]
At least with Django 1.9.9 -- the version I'm currently using --, note that .get_fields() actually also "considers" any foreign model as a field, which may be problematic. Say you have:
class Parent(models.Model):
id = UUIDField(primary_key=True)
class Child(models.Model):
parent = models.ForeignKey(Parent)
It follows that
>>> map(lambda field:field.name, Parent._model._meta.get_fields())
['id', 'child']
while, as shown by #Rockallite
>>> map(lambda field:field.name, Parent._model._meta.local_fields)
['id']
So before I found this post, I successfully found this to work.
Model._meta.fields
It works equally as
Model._meta.get_fields()
I'm not sure what the difference is in the results, if there is one. I ran this loop and got the same output.
for field in Model._meta.fields:
print(field.name)
In sometimes we need the db columns as well:
def get_db_field_names(instance):
your_fields = instance._meta.local_fields
db_field_names=[f.name+'_id' if f.related_model is not None else f.name for f in your_fields]
model_field_names = [f.name for f in your_fields]
return db_field_names,model_field_names
Call the method to get the fields:
db_field_names,model_field_names=get_db_field_names(Mymodel)
Combined multiple answers of the given thread (thanks!) and came up with the following generic solution:
class ReadOnlyBaseModelAdmin(ModelAdmin):
def has_add_permission(self, request):
return request.user.is_superuser
def has_delete_permission(self, request, obj=None):
return request.user.is_superuser
def get_readonly_fields(self, request, obj=None):
return [f.name for f in self.model._meta.get_fields()]
Why not just use that:
manage.py inspectdb
Example output:
class GuardianUserobjectpermission(models.Model):
id = models.IntegerField(primary_key=True) # AutoField?
object_pk = models.CharField(max_length=255)
content_type = models.ForeignKey(DjangoContentType, models.DO_NOTHING)
permission = models.ForeignKey(AuthPermission, models.DO_NOTHING)
user = models.ForeignKey(CustomUsers, models.DO_NOTHING)
class Meta:
managed = False
db_table = 'guardian_userobjectpermission'
unique_together = (('user', 'permission', 'object_pk'),)