I am trying to work with filtering db by query params from the link that looks like this:
{{url}}/api/books?author="XXX"&from=2003&to=2051&acquired=true
I already handled author and acquired params but am stuck with from and to. My filter.py looks like this:
class BookFilter(django_filters.FilterSet):
author = django_filters.CharFilter(field_name="authors__fullname", lookup_expr="icontains")
from = django_filters.NumberFilter(field_name="published_year", lookup_expr="gte")
to = django_filters.NumberFilter(field_name="published_year", lookup_expr="lte")
acquired = django_filters.BooleanFilter(field_name="acquired")
class Meta:
model = Book
fields = [
"author",
"from",
"to",
"acquired"
]
I am looking for a way to assign these query params without overwriting key words (from and to) which is obviously a terrible idea.
You don't need to define special BookFilter class. This task can simply done on view level. Just define a DateFilter class to filter according to the dates, and the other
type of filters like search by name or order by price can be implemented on view level. (Make sure your model have a created_at field).
Here is an example
from django_filters import rest_framework as rest_filters
from apps.yourappname.models import YourModel
from django_filters import rest_framework as rest_filters
class DateFilter(rest_filters.FilterSet):
created_at = rest_filters.DateFromToRangeFilter()
class Meta:
model = YourModel
fields = ['created_at']
class YourAPIView(ListAPIView):
serializer_class = YourModelSerializer
filter_backends = [filters.SearchFilter, filters.OrderingFilter,
rest_filters.DjangoFilterBackend, ]
filter_class = DateFilter
search_fields = ['authors__fullname' ]
ordering_fields = [ 'created_at', ]
def get_queryset(self):
dataset_queryset =
YourModel.objects.filter(id=self.request.user.id)
return dataset_queryset
When you filter by date, your URL will look like this:
http://127.0.0.1:8000/api/yourappname/id/files/?created_at_after=2022-04-19&created_at_before=2022-05-28
For anyone that may have same problem:
I managed to create a workaround in which I create copy of querydict, pop() "from" and "to" values and assign them to newly created "from_date" and "to_date" keys.
Related
I have an model which is for mapping book(item) to categories(tag),
it shows like this in the django admin page.
id item_uid tag_uid
407 Food Recipe
but in django swagger page, when I try to GET this mapping api with ID 407, it returned like this:
"id": 407,
"item_uid": "http://127.0.0.1:8000/items/237/";
"tag_uid": "http://127.0.0.1:8000/tags/361/"
as you can see, it mapped together correctly, but the response body showed the object url and it's object id, which is not readable for human users. I wonder that if there is anyway to make them like this:
"id": 407,
"item_uid": "Food";
"tag_uid": "Recipe"
edit: codes,
#models.py
class Map_item_tag(models.Model):
item_uid = models.ForeignKey(items, on_delete=models.CASCADE, verbose_name='Item UID')
tag_uid = models.ForeignKey(tags, on_delete=models.CASCADE, verbose_name='Tag UID')
#admin.py
#admin.register(Map_item_tag)
class map_item_tag_admin(ImportExportModelAdmin):
resource_class = map_item_tag_Resource
readonly_fields = ('id',)
list_display = ['id','item_uid','tag_uid']
#serializers.py
class Map_item_tag_Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Map_item_tag
fields = ['id','item_uid','tag_uid']
#views.py
class Map_item_tag_ViewSet(viewsets.ModelViewSet):
queryset = Map_item_tag.objects.all().order_by('item_uid')
serializer_class = Map_item_tag_Serializer
parser_classes = (FormParser, MultiPartParser)
permission_classes = [permissions.IsAuthenticated]
thank you for answering!
It seems you are using a HyperlinkedModelSerializer instead of a regular ModelSerializer
Try changing the serializer class to a ModelSerializer:
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = [] # list of fields you want to include in your Item serializer
class Map_item_tag_Serializer(serializers.ModelSerializer):
item_uid = ItemSerializer()
class Meta:
model = Map_item_tag
fields = ['id','item_uid','tag_uid']
In addition, I would advise you to use CamelCase notation for all your classes. For example: instead of using Map_item_tag_Serializer, change the name to MapItemTagSerializer. The same goes for all your other classes.
I would also avoid using using the _uuid suffix when using ForeignKey relationships. In the MapItemTag model, the ForeignKey relationship inherently means that the field will point to an object Item of Tag object. Hence, no need to specify the _uuid part again.
For example, the following changes would make the model a lot more readable:
class MapItemTag(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE, verbose_name='map_item')
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, verbose_name='map_tag')
In my models.py I have the following classes:
class Project(models.Model):
name = models.CharField(max_length=100)
class ProjectMaterial(models.Model):
project = models.ForeignKey("Project", on_delete=models.CASCADE)
material = models.CharField(max_length=150)
units = models.IntegerField()
My serializers are like this:
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = "__all__"
class ProjectMaterialSerializer(serializers.ModelSerializer):
class Meta:
model = ProjectMaterial
fields = "__all__"
My current views.py looks like this:
class ProjectList(generics.ListCreateAPIView):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
class ProjectMaterialList(generics.ListCreateAPIView):
queryset = ProjectMaterial.objects.all()
serializer_class = ProjectMaterialSerializer
How should I create my urlpatterns to make a PUT request to change the units value for a
project with an id=1 for a material with an id=3?
I suppose you want to change the value of a Material Object where id = 3. in this case you really dont want to add the FK to the url_patterns. instead you can send data data related to FK via a PUT request.
urlpatterns = [
path('<id>/edit/', MaterialUpdateView.as_view(), name='material-update'),
]
If you really want to change the FK. send the data via a PUT or PATCH request like this
data = {
id: 3,
project: 1,
material: "some material"
units: 25,
}
If you want to update "ProjectMaterial" record with id=3 and that has FK relationship to "Project" record with id=1. All you need is "ProjectMaterial" id in URL and the data that needs to be updated for the corresponding "Project" record(Since it is in relationship with ProjectMaterial).
urlpatterns = [
path('/material/<id>/', ProjectMaterialDetail.as_View(), name='project_material')
]
If you want to update only the "units" field of "ProjectMaterial", you just inherit UpdateModelMixin into the new view class, "ProjectMaterialDetail". You can inherit "RetrieveModelMixin" into the same class. All you need to do is to make sure you send data in correct format to the ProjectMaterial serializer in "PUT" method of "ProjectMaterialDetail" view.
{
id: 5,
units: 152,
}
You can override Update method in serializer or you can call "partial_update" method in "PUT" method.
I have a model containing a ForeignKey to another model. I am attempting to serialize this model and want to control what field is returned for the foreignkey field. See below:
models.py
class Surveyor(models.Model):
num = models.CharField(max_length=3)
name = models.CharField(max_length=250)
class Anblsrecord(models.Model):
...
sur_num = models.ForeignKey(Surveyor, on_delete=models.CASCADE)
views.py
def anbls_points(request):
points_as_geojson = serialize('geojson', Anblsrecord.objects.all()[:5], fields=(... 'sur_num'))
return JsonResponse(json.loads(points_as_geojson))
When I view this I get:
... "sur_num": 1 ...
where the "1" is "num" from Surveyor class. I want to return "name".
I looked at https://docs.djangoproject.com/en/2.2/topics/serialization/ which talks about multi-table inheritance, but I can't find anything for a related table.
Any help would be greatly appreciated.
Django Rest Framework serializers with django-rest-framework-gis worked:
serializers.py
from anblsrecords import models
from rest_framework_gis.serializers import GeoFeatureModelSerializer
class AnblsrecordSerializer(GeoFeatureModelSerializer):
sur_name = serializers.CharField(source='sur_num.name')
class Meta:
model = models.Anblsrecord
geo_field = "geom"
fields = (
...
'sur_name',
)
views.py
from rest_framework import generics
class ListAnbls_points(generics.ListCreateAPIView):
queryset = Anblsrecord.objects.all()[:5]
serializer_class = serializers.AnblsrecordSerializer
This returns:
"properties": {
...,
"sur_name": "Name of Surveyor",...}, and includes the geometry feature.
TL;DR: What could be the reason the incoming data for one of my serializers does not get processed?
I'm working on a serializer for a nested relationship. The serializer should get a list of UUIDs, so that I can make many to many relationships. Here is the model:
class Order(
UniversallyUniqueIdentifiable,
SoftDeletableModel,
TimeStampedModel,
models.Model
):
menu_item = models.ForeignKey(MenuItem, on_delete=models.CASCADE)
custom_choice_items = models.ManyToManyField(CustomChoiceItem, blank=True)
price = models.ForeignKey(MenuItemPrice, on_delete=models.CASCADE)
amount = models.PositiveSmallIntegerField(
validators=[MinValueValidator(MINIMUM_ORDER_AMOUNT)]
)
Here is the data (my post body) with which I hit the route in my tests:
data = {
"checkin_uuid": self.checkin.uuid,
"custom_choice_items": [],
"menu_item": self.menu_item.uuid,
"price": self.menu_item_price.uuid,
"amount": ORDER_AMOUNT,
}
response = self.client.post(self.order_consumer_create_url, self.data)
Note that the empty list for custom_choice_items does not change anything. Even if I fill it with values the same error occurs. And last but not least here are the serializers:
class CustomChoiceItemUUIDSerializer(serializers.ModelSerializer):
"""Serializer just for the uuids, which is used when creating orders."""
class Meta:
model = CustomChoiceItem
fields = ["uuid"]
....
# The serializer that does not work
class OrderSerializer(serializers.ModelSerializer):
menu_item = serializers.UUIDField(source="menu_item.uuid")
custom_choice_items = CustomChoiceItemUUIDSerializer()
price = serializers.UUIDField(source="price.uuid")
wish = serializers.CharField(required=False)
class Meta:
model = Order
fields = [
"uuid",
"menu_item",
"custom_choice_items",
"price",
"amount",
"wish",
]
The problem is now, that when I leave out many=True, I get the error:
{'custom_choice_items': [ErrorDetail(string='This field is required.', code='required')]}
And If I set many=True I just simply don't get any data. By that I mean e.g. the value of validated_data["custom_choice_items"] in the serializers create() method is just empty.
What goes wrong here?
I even checked that the data is in the request self.context["request"].data includes a key custom_choice_items the way I pass the data to this view!
EDIT: Here is the data I pass to custom_choice_items:
data = {
“checkin_uuid”: self.checkin.uuid,
“custom_choice_items”: [{“uuid”: custom_choice_item.uuid}],
“menu_item”: self.menu_item.uuid,
“price”: self.menu_item_price.uuid,
“amount”: ORDER_AMOUNT,
}
self.client.credentials(HTTP_AUTHORIZATION=“Token ” + self.token.key)
response = self.client.post(self.order_consumer_create_url, data)
When you post data using the test api client, if the data contains nested structure you should use format=json, like this:
response = self.client.post(self.order_consumer_create_url, data, format='json')
Did you override .create method in the serializer? Something like this should work:
from django.db import transaction
class OrderSerializer(serializers.ModelSerializer):
# your fields and Meta class here
#transaction.atomic
def create(self, validated_data):
custom_choice_items = validated_data.pop('custom_choice_items')
order = super().create(validated_data)
order.custom_choice_items.add(*custom_choice_items)
return order
By the way you don't really need to define CustomChoiceItemUUIDSerializer if is just the primary key of that.
I am trying to get a simple nested route set up with drf-extensions but am having trouble following the docs, I am getting this error:
TypeError at /data/
register() got an unexpected keyword argument 'parents_query_lookups'
Trying to achieve /data/Survey/<survey>/Version/<version>/Product/<product>/
each survey has multiple versions and those versions will contain multiple Products e.g., /data/survey/survey1_name/version/survey1_version1/product/survey1_version_product1/
but currently I have un-nested endpoints
/data/survey/
/data/versions/
/data/products/
models.py
class Survey(models.Model):
survey = models.CharField(choices=SURVEYS, max_length=100)
class Version(models.Model):
version = models.CharField(choices=DATA_RELEASES, max_length=50)
survey = models.ForeignKey(Survey)
class Product(models.Model):
product = models.CharField(choices=PRODUCTS, max_length=100)
version = models.ForeignKey(Version)
views.py
class SurveyViewSet(NestedViewSetMixin, viewsets.ModelViewSet):
queryset = Survey.objects.all()
serializer_class = SurveySerializer
permission_classes = [permissions.AllowAny]
class VersionViewSet(NestedViewSetMixin, viewsets.ModelViewSet):
queryset = Version.objects.all()
serializer_class = VersionSerializer
permission_classes = [permissions.AllowAny]
class ProductViewSet(NestedViewSetMixin, viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
permission_classes = [permissions.AllowAny]
serializers.py
class SurveySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Survey
fields = ('id', 'survey')
class VersionSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Version
fields = ('id', 'version', 'survey')
class ProductSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Product
fields = ('id', 'product', 'version')
urls.py
router = ExtendDefaultRouter()
router.register(r'surveys', views.SurveyViewSet, base_name='survey')
router.register(r'versions', views.VersionViewSet, parents_query_lookups=['survey']),
router.register(r'products', views.ProductViewSet, parents_query_lookups=['version'])
urlpatterns = [
url(r'^data/', include(router.urls)),
]
You should chain the calls to register(), instead of calling them directly on the router :
urls.py
router = ExtendDefaultRouter()
(router.register(r'surveys', views.SurveyViewSet, base_name='survey')
.register(r'versions', views.VersionViewSet, parents_query_lookups=['survey']),
.register(r'products', views.ProductViewSet, parents_query_lookups=['version']))
That's because NestedRouterMixin.register() returns an instance of NestedRegistryItem, which is the class that understand the parents_query_lookups parameter.
In order to keep your project well organized in accordance with the hierarchy of your data, the first thing I would do is separate each tier of urls into a separate entity, and possibly even a separate file. The second would be to add an primary-key endpoint to each tier of the rest API. In the end it would look something like this:
survey_urls.py
router.register(r'survey', views.SurveyViewSet, base_name='survey')
router.register(r'survey-version/', include('my_app.rest_server.version_urls'))
version_urls.py
router.register(r'version', views.VersionViewSet, base_name='version')
router.register(r'version-product/', include('my_app.rest_server.product_urls'))
product_urls.py
router.register(r'product/(?P<pk>[0-9]+)$', views.ProductViewSet, base_name='product')
Then, your final url could look like: /data/survey-version/33/version-product/5/product/8
In all likelihood, this is an unnecessary complication of things, since each product has its own unique id, and then you can just access it with a url like: /data/product/pk, using a single url routing, namely the last line of code above.
My examples assumed the records have a numeric primary key, if the case is otherwise you'd have to change the regex accordingly.