Django documents state we can override default queryset using manager's get_queryset method.
What if I user models.Queryset and as.manager method to avoid duplicate methods. How can I override default query?
It seems get_queryset method of models.Queryset doesnt seems to work in this case.
Thanks.
You can use a combination of a Manager and a QuerySet to achieve this, like so:
# model_managers.py
class CustomManager(models.Manager):
""" Enables changing the default queryset function. """
def get_queryset(self):
# Here you can change the default queryset function
return super(CustomManager, self).get_queryset(example_field=True)
class CustomQuerySet(models.QuerySet):
""" Include additional queryset functions. """
def example_filter(self):
return self.filter(other_field=False)
# models.py
class YourModel(models.Model):
# Override the default manager
objects = CustomManager.from_queryset(CustomQuerySet)() # The pair of empty parenthesis is required!
And then you can use it anywhere like:
# Will filter `example_field`
YourModel.objects.all()
# Will filter `example_field` and `other_field`
YourModel.objects.example_filter()
You should show what you tried that didn't work.
But nevertheless, you just need to use that call to define your own manager in the model:
class MyModel(models.Model):
...
objects = MyQuerySet.as_manager()
You can create manage class by inheriting models.Manager and then you can define your own query set method.
in model.py, we should assign class as property as given below:
example = ExampleManager()
Now we can use example instead of objects and can call respective ExampleManager method.
Related
I want to have a FilterSet class that allows all of the fields to be filtered as a list.
I was thinking of overriding the default FilterSet class of django-filters and having the fields be dynamically set as well as setting a method to each one.
So, something like this for example:
ModelName_fields = {'names': 'name', 'ids': 'id', 'second_ids': 'second_id'}
class ListFilter(FilterSet):
def __init__(self, *args, **kwargs):
# self._meta.fields = [field_name for field_name in modelNameHere + '_fields']
super(FilterSet).__init__(*args)
class SomeAPIView(mixins.ListModelMixin):
model = ModelName
filterset_class = ListFilter
Basically, ModelName_fields is a declared constant that maps the query parameter to the field name of the model. In this case, I declare the model on the view as well as the filterset class and in the __init__ method of the filterset class, I dynamically attach the fields as well as the query parameter name.
In all essence, I just want to make the ListFilter as generic as possible to be used on different views as well.
My question is, is this the correct way or is there some other better way to accomplish this? Also, how can I get the name of the model, which is an attribute of the view class, in the ListFilter class?
What I Need
I want to have a global configuration for my app and I want to reuse a generic UpdateView.
What I Tried
For this purpose I created a model (example fields):
class Configuration(models.Model):
admin = models.ForeignKey('User', on_delete=models.CASCADE)
hostname = models.CharField(max_length=23)
A generic Updateview:
class ConfigurationView(UpdateView):
model = Configuration
fields = ['admin','hostname']
And urls.py entry
path(
'configuration/',
views.ConfigurationView.as_view(
queryset=Configuration.objects.all().first()
),
name='configuration'
),
As you can see I want the configuration/ path to link to this configuration and always only edit this one object.
Problem
I get the error
AttributeError: 'Configuration' object has no attribute 'all'
Questions
How can I hardcode the object into the path in urls.py so that always the first Configuration object is used for the UpdateView?
Is there a better way to do this? I simply want to have a global configuration object and want it to be editable and displayable with a template of my choice.
You're trying to provide a single object to a class expecting a queryset. The view calls get_queryset which does this;
def get_queryset(self):
"""
Return the `QuerySet` that will be used to look up the object.
This method is called by the default implementation of get_object() and
may not be called if get_object() is overridden.
"""
if self.queryset is None:
if self.model:
return self.model._default_manager.all()
else:
raise ImproperlyConfigured(
"%(cls)s is missing a QuerySet. Define "
"%(cls)s.model, %(cls)s.queryset, or override "
"%(cls)s.get_queryset()." % {
'cls': self.__class__.__name__
}
)
return self.queryset.all()
You've provided a queryset so that lands on self.queryset.all() which for your example is calling all() on an instance of your class.
To use the queryset kwarg of as_view() you'd do something like MyView.as_view(queryset=MyModel.objects.filter(enabled=True))
So you need to change the way the view looks for the object;
class ConfigurationView(UpdateView):
def get_object(self):
return Configuration.objects.first()
By default UpdateView does this to get an object; https://ccbv.co.uk/projects/Django/2.0/django.views.generic.edit/UpdateView/
If you're limiting the config to 1 object you will also want to implement a Singleton design. Essentially this is a way to ensure only 1 object can exist. Read more here; https://steelkiwi.com/blog/practical-application-singleton-design-pattern/
There is a really helpful package for singletons called django-solo
class User(generics.RetrieveAPIView):
serializer_class = RetrieveLocalSerializer
queryset = User.objects.filter(
fields_1=True,
fields_2=False
)
class LocalSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('field_1', 'field_2', 'field_3',)
The API did not work as it I wish. When I tried get user that does not have the property i want, it still returned the result.
I even tried override that function but it did not work too.
def get_queryset(self):
return User.objects.filter(
is_localguide=True,
state=PROFILE_STATE.PUBLISHED
)
Any help is appreciated.
If I understood your question correctly you wish to get list of instances in your view (using Django Rest Framework). The problem is that your view is inheriting from a generics.RetrieveAPIView. This view class calls self.retrieve(request, *args, **kwargs) method which returns you an object, not queryset. I think that you should inherit your view from a ListAPIView class. This class inherits ListModelMixin which
Provides a .list(request, *args, **kwargs) method, that implements listing a queryset.
So your code will be looking like this:
class User(generics.ListAPIView):
serializer_class = RetrieveLocalSerializer
queryset = User.objects.filter(
fields_1=True,
fields_2=False
)
See http://www.django-rest-framework.org/api-guide/generic-views/#listapiview for more information.
You may either define your queryset in a view or override get_queryset method:
queryset - The queryset that should be used for returning objects from this view. Typically, you must either set this attribute, or override the get_queryset() method. If you are overriding a view method, it is important that you call get_queryset() instead of accessing this property directly, as queryset will get evaluated once, and those results will be cached for all subsequent requests.
You may find more information here: http://www.django-rest-framework.org/api-guide/generic-views/#genericapiview
Hope this will help)
I am trying to use django-autocomplete-light but I have some problems.
I would like to filter the queryset in the ModelChoiceField.
If I don't use auto-complete my result selection is correct but if I use widget it doesn't work correctly, it shows all records.
Here is my code:
class MyModelAdminForm(forms.ModelForm):
def __init__(self, *args, **kw):
super(MyModelAdminForm, self).__init__(*args, **kw)
self.fields['my_field'] = forms.ModelChoiceField(
MyModel.objects.filter(status=1),
widget=autocomplete_light.ChoiceWidget('MyModelAutocomplete')
)
class MyModelAdmin(ModelAdmin):
form = MyModelAdminForm
You should set MyModelAutocomplete.choices, either via register():
autocomplete_light.register(MyModel, choices=MyModel.objects.filter(status=1))
Or within the class:
class MyModelAutocomplete(autocomplete_light.AutocompleteModelBase):
choices = MyModel.objects.filter(status=1)
Refer to docs for more:
AutocompleteModel API docs
Using register() to pass class attributes: "In addition, keyword arguments will be set as class attributes."
Overriding choices_for_request() might be useful if you need to filter choices based on the user.
I would like to automate this, but the widget isn't aware about the form field instance unfortunately.
Apply the filter inside MyModelAutocomplete by defining a method
class MyModelAutocomplete(autocomplete_light.AutocompleteModelBase):
choices=MyModel.objects.all()
def choices_for_request(self):
choices = choices.filter(status=1)
return self.order_choices(choices)[0:self.limit_choices]
choices_for_request is mostly used for dynamic filterming
I was trying to figure out how to do this within the autocomplete-light documentation. I figured out how, but not without a bit of digging, so hopefully this is helpful.
In the autocomplete_light_registry.py file, fill out the "name" and "choices" parameters:
#myapp/autocomplete_light_registry.py
autocomplete_light.register(MyModel,
#... Other Parameters ...
name = 'SpecialMyModelAutocomplete',
choices = YourQuerySetHere, #e.g. MyModel.objects.filter(...)
)
The default name is "MyModelAutocomplete" so if you include more than one registered autocomplete for a model, you need to specify which one you want to use (otherwise it uses the first one in the registry, NOT the default).
To specify, use "autocomplete_names" which is (from the docs) "A dict of field_name: AutocompleteName to override the default autocomplete that would be used for a field." In my case I'm using it within the django admin.
#myapp/admin.py
class MyModelAdminForm(autocompletelight.ModelForm):
class Meta:
model = MyModel
autocomplete_names = {'field_name':'SpecialMyModelAutocomplete'}
Note that you don't need to include any fields for which you want to use the default Autocomplete in autocomplete_names. Incidentally "autocomplete_exclude" and "autocomplete_fields" may also be of interest here and are analogous to "fields" and "exclude" in a ModelAdmin to specify which fields to include/exclude from using autocomplete.
Addition:
You can also use "autocomplete_names" in the modelform_factory:
form = autocomplete_light.modelform_factory(MyOtherModel,autocomplete_names={MyFieldName:'MyModelAutocomplete'}) #where MyFieldName is a ForeignKey to MyModel
From the docs:
autocomplete_light.forms.modelform_factory(model,autocomplete_fields=None,autocomplete_exclude=None,autocomplete_names=None,registry=None,**kwargs)
I have an order model with a followed_by field:
class order(models.Model):
followed_by = models.ForeignKey(User, limit_choices_to={'groups__name': "Managers"})
I have several such models and forms for those models. By default the form displays a modelchoicefield listing users that are mangers. This is fine. But the display isn't nice: it gives the username, and I want first+last name. This would work nicely: Change Django ModelChoiceField to show users' full names rather than usernames
except that now in everyform I must declare the queryset to limit users to managers. Can I use the above method so that the custom modelchoicefield defaults to my filtered queryset. so then from a form I can just say:
followed_by = ManagerUserModelChoiceField()
Can you define the queryset on your ModelChoiceField child class?
class UserModelChoiceField(ModelChoiceField):
# Query that returns set of valid choices
queryset = User.objects.filter(group__name='Managers')
def label_from_instance(self, obj):
return obj.get_full_name()
Try passing in the queryset as an argument to the ManagerUserModelChoiceField class.
followed_by = ModelChoiceField(queryset = User.objects.filter(groups__name="Managers")
After my comment to #Enrico this thought occurred to me: I overwrote the "init" class on my custom field like so:
class UserModelChoiceField(forms.ModelChoiceField):
def __init__(self, *args, **kwargs):
super(UserModelChoiceField, self).__init__(queryset=User.objects.filter(groups__name="Managers"), *args, **kwargs)
I've seen stuff like this done in python before but I'm new to python so I'm not sure if this is a bad thing to do or if I should make this better somehow? I'd appreciate some feedback. That being said, it seems to be working correctly.