i have two models
class Sku(models.Model):
manufacturer = models.ForeignKey('Manufacturer')
class Manufacturer(models.Model):
title = models.CharField()
I want that in the filtering appeared only manufacturers associated with the current set of sku.
my view part:
c['skus'] = self.object.skus.filter(hide=False, prices__price_type=PRICE_ROZN).prefetch_related('prices',
'stock').all().order_by(
'prices__value')
sku_filter = SkuFilter(self.request.GET, c['skus'])
If Self existed at this moment, I would filter out the manufacturers in this way:
class SkuFilter(django_filters.FilterSet):
# manufacturer__title = django_filters.CharFilter(lookup_expr='icontains')
manufacturer = django_filters.filters.ModelMultipleChoiceFilter(
name='manufacturer',
to_field_name='title',
queryset=Manufacturer.objects.filter(
pk__in=self.queryset.objects.values_list('manufacturer').distinct()),
)
class Meta:
model = Sku
fields = ['manufacturer', ]
But it is obvious that at the given moment the self does not yet exist.
I solved this question by this method:
create field without filttration like (Manufacturer.objects.all())
wait for metaclass magic create base_fitler field
override init and replace current filter
class SkuFilter(django_filters.FilterSet):
def __init__(self, data=None, queryset=None, prefix=None, strict=None):
self.base_filters['manufacturer'] = django_filters.filters.ModelMultipleChoiceFilter(
name='manufacturer',
to_field_name='title',
queryset=Manufacturer.objects.filter(
pk__in=queryset.values_list('manufacturer').distinct()),
)
super().__init__(data, queryset, prefix, strict)
manufacturer = django_filters.filters.ModelMultipleChoiceFilter(
name='manufacturer',
to_field_name='title',
queryset=Manufacturer.objects.all()
)
class Meta:
model = Sku
fields = ['manufacturer', ]
Related
I've got a database with an simple Employee model and node in Django. I´m using Graphene to create an API around this that allows a user to retrieve the right data.
class Employee(models.Model):
id = models.UUIDField(primary_key=True, unique=True, )
name = models.CharField(max_length=128)
class EmployeeNode(DjangoObjectType):
class Meta:
model = Employee
fields = "__all__"
interfaces = (graphene.relay.Node, )
Now in addition to this, I have a query that finds a "buddy" for every Employee, which is another Employee (ID) in the database, and a function (details irrelevant here) that finds the correct "buddy" in the database using some not further specified Django query.
class EmployeeNodeWithBuddy(DjangoObjectType):
buddy_id = graphene.UUID()
class Meta:
model = Employee
fields = "__all__"
interfaces = (graphene.relay.Node, )
#classmethod
def get_queryset(cls, queryset, info):
set_with_buddy_annotation = queryset.annotate(
buddy_id=ExpressionWrapper(Subquery(
### Omitting the details of this query ###
).only('id')[:1], output_field=models.UUIDField()
), output_field=models.UUIDField())
)
return set_with_buddy_annotation
This works ok, but what I actually want is not the ID of the buddy, but the actual EmployeeNode. I can´t figure out if there is a good way to annotate/add info to this query to make it return the thing I want. It would look like:
class EmployeeNodeWithBuddy(DjangoObjectType):
buddy_id = graphene.UUID()
buddy = graphene.Field(EmployeeNode) # Field with EmployeeNode instead of just ID!
class Meta:
model = Employee
fields = "__all__"
interfaces = (graphene.relay.Node, )
#classmethod
def get_queryset(cls, queryset, info):
set_with_buddy_annotation = queryset.annotate(
buddy_id=ExpressionWrapper(Subquery(
### Omitting the details of this query ###
).only('id')[:1], output_field=models.UUIDField()
), output_field=models.UUIDField())
)
set_with_buddy_annotation = # Somehow fetch the EmployeeNode from the buddy_id here?
return set_with_buddy_annotation
Does this make sense to do and is this even possible?
So I have a Django project with Django REST Framework with large number of models. For frontend to be user friendly I should display not only related object's id but also name. My idea for the solution was to replace all the PrimaryKeyRelated fields with StringRelatedFields in serializers on response. As the number of models is large I decided to make a single abstract serializer/mixin and intercept field creation replacing the field if is of correct type. This is how far I got up to now:
class AbstractSerializer(serializers.ModelSerializer):
class Meta:
model: AbstractModel = AbstractModel
read_only_fields: list = [
'created_at',
'created_by',
'modified_at',
'modified_by',
'is_deleted',
'deleted_at',
'deleted_by'
] + ['is_active'] if 'is_active' in [field.attname for field in model._meta.fields] else []
abstract: bool = True
def to_representation(self, instance):
serializer = AbstractRequestResponseSerializer(instance)
return serializer.data
class AbstractRequestResponseSerializer(AbstractSerializer):
class Meta(AbstractSerializer.Meta):
pass
#classmethod
def _get_declared_fields(cls, bases, attrs):
fields = [(field_name, attrs.pop(field_name))
for field_name, obj in list(attrs.items())
if isinstance(obj, Field)]
fields.sort(key=lambda x: x[1]._creation_counter)
new_fields = []
for field in fields:
if isinstance(field, PrimaryKeyRelatedField):
field = StringRelatedField(source=field.source, required=False)
new_fields.append(field)
fields = new_fields
known = set(attrs)
def visit(name):
known.add(name)
return name
base_fields = [
(visit(name), f)
for base in bases if hasattr(base, '_declared_fields')
for name, f in base._declared_fields.items() if name not in known
]
return OrderedDict(base_fields + fields)
This gives an infinite loop error because of __new__ method and I started to wonder if I am overriding the right function. I also tried to replace to_representation function but I guess that function occurs too late in the flow when all the field instances are created already. Which function should I override?
class ParentModelSerializer(serializers.ModelSerializer):
class Meta:
model = ParentModel
fields = '__all__'
class ChildModelSerializer(serializers.ModelSerializer):
parent = ParentModelSerializer(read_only=True)
class Meta:
model = ChildModel
fields = '__all__'
Or if you want to display the children in your parent:
class ParentModelSerializer(serializers.ModelSerializer):
children = ChildModelSerializer(read_only=True, many=True)
# children is the "related_name"
class Meta:
model = ParentModel
fields = '__all__'
class ChildModelSerializer(serializers.ModelSerializer):
class Meta:
model = ChildModel
fields = '__all__'
Maybe I phrased the question incorrectly (you could rephrase that to help future generations :) ), but the solution I made looks like this:
class AbstractSerializer(serializers.ModelSerializer):
class Meta:
model: AbstractModel = AbstractModel
read_only_fields: list = [
'created_at',
'created_by',
'modified_at',
'modified_by',
'is_deleted',
'deleted_at',
'deleted_by'
] + ['is_active'] if 'is_active' in [field.attname for field in model._meta.fields] else []
abstract: bool = True
def to_representation(self, instance):
ret = OrderedDict()
fields = self._readable_fields
for field in fields:
if isinstance(field, PrimaryKeyRelatedField):
parent = field.parent
field_name = field.field_name
source = field.source
if source != field_name:
field = StringRelatedField(source=field.source, required=False)
else:
field = StringRelatedField(required=False)
field.bind(field_name, parent)
try:
attribute = field.get_attribute(instance)
except SkipField:
continue
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
if check_for_none is None:
ret[field.field_name] = None
else:
ret[field.field_name] = field.to_representation(attribute)
return ret
I would like to have a form with the preselected checkboxes of a ManyToManyField.
models.py
class Store(models.Model):
...
class Brand(models.Model):
stores = models.ManyToManyField(Store, blank=True, related_name="brands")
forms.py
class StoreForm(ModelForm):
class Meta:
model = Store
fields = ('brands',)
I get this exception:
django.core.exceptions.FieldError: Unknown field(s) (brands) specified for Store
I know that I can add the field manually to the class:
brands = forms.ModelMultipleChoiceField(
queryset=Brand.objects.all(),
widget=forms.CheckboxSelectMultiple,
)
If I do this the checkboxes are not preselected.
How is it possible to include the ManyToMany field from "the other side" of the model (from Store)?
#hedgie To change the field in the other model is not a good option for me because I use it already.
But the __init__() was a good hint. I come up with this solution and it seems to work.
class StoreForm(ModelForm):
def __init__(self, *args, **kwargs):
if kwargs.get('instance'):
brand_ids = [t.pk for t in kwargs['instance'].brands.all()]
kwargs['initial'] = {
'brands': brand_ids,
}
super().__init__(*args, **kwargs)
# https://stackoverflow.com/questions/49932426/save-many-to-many-field-django-forms
def save(self, commit=True):
# Get the unsaved Pizza instance
instance = forms.ModelForm.save(self, False)
# Prepare a 'save_m2m' method for the form,
old_save_m2m = self.save_m2m
def save_m2m():
old_save_m2m()
# This is where we actually link the pizza with toppings
instance.brands.clear()
for brand in self.cleaned_data['brands']:
instance.brands.add(brand)
self.save_m2m = save_m2m
# Do we need to save all changes now?
# Just like this
# if commit:
instance.save()
self.save_m2m()
return instance
brands = forms.ModelMultipleChoiceField(
queryset=Brand.objects.all(),
widget=forms.CheckboxSelectMultiple,
)
Though it seems to be not very elegant. I wonder why django does not support a better way.
One possibility is to define the field on the "other" model. So instead of writing this:
class Store(models.Model):
...
class Brand(models.Model):
stores = models.ManyToManyField(Store, blank=True, related_name="brands")
You can write this:
class Brand(models.Model):
...
class Store(models.Model):
brands = models.ManyToManyField(Brand, blank=True, related_name="stores")
Or, if you have manually added the field to the form, you could populate its initial value in the form's __init__() method.
I have the following (simplified) data model:
class Article(Model):
uuid = models.CharField(primary_key=True, max_length=128)
class Attribute(Model):
uuid = models.CharField(primary_key=True, max_length=128)
article = models.ForeignKey(Article, related_name='attributes')
type = models.CharField(max_length=256)
value = models.CharField(max_length=256)
An example usage would be an article with an attribute attached to it with type="brand" and value="Nike". Now I want to write an API which can get all articles with a certain brand, but I can't seem to write the filter for it. This is what I have so far:
class PhotobookFilter(df.FilterSet):
brand = df.CharFilter(method='filter_brand')
class Meta:
model = Article
def filter_brand(self, queryset, name, value):
return queryset.filter('order__attributes')
class PhotobookViewSet(AbstractOrderWriterViewSet):
queryset = Article.objects.all()
serializer_class = ArticlePhotobookSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_class = PhotobookFilter
The line with queryset.filter is obviously not correct yet. I need to create a filter here that returns all articles that contain an attribute with type="brand" and value=value. How would I do this?
Are you sure you want to condense both lookups (type and value of Attribute) into one filter? Why not allow filtering on both fields separately?
E.g.
class PhotobookFilter(df.FilterSet):
type = df.CharFilter(method='filter_type')
value = df.CharFilter(method='filter_value')
class Meta:
model = Article
def filter_type(self, queryset, name, value):
return queryset.filter(**{'attributes__type': value})
def filter_value(self, queryset, name, value):
return queryset.filter(**{'attributes__value': value})
And now a query like ?type=brand&value=Nike should work.
Obviously you could condense both conditions into one filter and for example hard code the band part:
class PhotobookFilter(df.FilterSet):
brand = df.CharFilter(method='filter_brand')
def filter_brand(self, queryset, name, value):
return queryset.filter(**{'attributes__type': 'brand', 'attributes__value': value})
But keeping them separate feels way more flexible.
You could also filter in reverse like this:
class PhotobookFilter(df.FilterSet):
brand = df.CharFilter(method='filter_brand')
class Meta:
model = Article
def filter_brand(self, queryset, name, value):
articles = Attribute.objects.filter(type="brand", value=value).values_list('article_id', flat=True)
return queryset.filter(id__in=articles)
This will create subquery for Attribute, which will still be one sql request in the end
Use search_fields .For correct result rename your 'type' attribute name http://www.codesend.com/view/09ca65d42248fe1d89d07ce151f4f050/
I have a parent and a one-to-one related child model and I would like to render the fields from the child flat in the parent representation (read only). Currently, I have achieved that with a custom to_representation implementation but that seems very involved and I wonder if there is no easier way to achieve this.
It is made more complicated by the fact that my related model is connected via a property.
So here is the concrete example:
By default a related object would be rendered like:
{
parent_name:'Bob',
child:{
name:'Alice'
}
}
This is what I want and currently get with my to_representation:
{
parent_name:'Bob',
child_name:'Alice'
}
My models look like this:
class ChildModel(models.Model):
name = models.CharField(max_length=100, null=True)
class ParentModel(models.Model):
name = models.CharField(max_length=100, null=True)
_child = models.ForeignKey('ChildModel', null=True)
#property
def child(self):
return self._most_recent_status
#name.setter
def child(self, value):
self._child = value
Here are my serializers:
class FlatChildField(serializers.RelatedField):
def to_representation(self, value):
return value.name
class FlatParentSerializer(serializers.ModelSerializer):
parent_name = serializers.CharField(source='name', read_only=True)
child_name = FlatChildField(source='_child', read_only=True)
class Meta:
model = Parent
fields = ('name', 'child_name')
For a simpler solution to get a flat representation of related models I would be grateful.
For completeness, I would be interested to hear if there is a simpler solution for "normal" related models (i.e. not property model fields as well). I was looking for the equivalent of the django model query syntax of related_model__field, but I cannot find that. Does that exist for django rest framework?
Many thanks
The simplest means would be to use source:
class FlatParentSerializer(serializers.ModelSerializer):
parent_name = serializers.CharField(source='name', read_only=True)
child_name = serializers.CharField(source='_child.name', read_only=True)
class Meta:
model = Parent
fields = ('name', 'child_name')
You can use SerializerMethodField, it saves you really a lot of work and it's so clean and trivial:
class FlatParentSerializer(serializers.ModelSerializer):
parent_name = serializers.CharField(source='name', read_only=True)
child_name = serializers.SerializerMethodField('get_child_name')
class Meta:
model = Parent
fields = ('name', 'child_name')
def get_child_name(self, obj):
return obj._child.name