Django: defining a generic manager in an Abstract model - django

Using the following code and defining suitable NATURAL_KEY per every class fails (instead of defining different mangers in every class, which replicate the same code):
class NexchangeManager(models.Manager):
def get_by_natural_key(self, param):
if param == "*":
return self.all()
lookup = {self.NATURAL_KEY: param}
return self.get(**lookup)
class NexchangeModel(models.Model):
class Meta:
abstract = True
objects = NexchangeManager()
Djagno complains about fields replication, although NexhcnageModel is an Abstract model.
Should I use a mixin instead?
error:
django.core.serializers.base.DeserializationError: Problem installing fixture '/Users/beoleg/dev/nexchange/core/fixtures/pairs_cross.json': 'NexchangeManager' object has no attribute 'NATURAL_KEY': (core.pair:pk=1) field_value was '['LTC']'
The purpose of this, a bit overcomplicated code at first glance is, to have something like this in my fixtures:
[
{
"model": "payments.paymentpreference",
"pk": 8,
"fields": {
"user": ["onit"],
"identifier": "paypal#nexchange.co.uk",
"payment_method": 12,
"comment": "Please send the funds as a personal payment (this is a precaution to prevent charge backs, payments for goods and services will be automatically declined)",
"currency": [
["*"]
],
"created_on":"2016-11-01T17:41:28+00:00",
"modified_on":"2016-11-01T17:41:28+00:00"
}
}
]
Instaed of:
[
{
"model": "payments.paymentpreference",
"pk": 8,
"fields": {
"user": ["onit"],
"identifier": "paypal#nexchange.co.uk",
"payment_method": 12,
"comment": "Please send the funds as a personal payment (this is a precaution to prevent charge backs, payments for goods and services will be automatically declined)",
"currency": [
["USD"],
["RUB"],
["EUR"],
["GBP"],
["JPY"],
["HRK"],
["CHF"],
["PLN"],
["RON"],
["BGN"],
["CZK"],
["AUD"],
["CAD"],
["NOK"],
["SEK"],
["DKK"],
["HUF"],
["TRY"],
["ZAR"],
["NZD"],
["BRL"],
["IDR"],
["ILS"],
["INR"],
["KRW"],
["MXN"],
["MYR"],
["PHP"],
["THB"]
],
"created_on":"2016-11-01T17:41:28+00:00",
"modified_on":"2016-11-01T17:41:28+00:00"
}
}
]

I don't understand the way you write your manager. If the NATURAL_KEY is an attribute of each model, I would write:
class NexchangeManager(models.Manager):
def get_by_natural_key(self, param):
qs = self.get_queryset()
if param == "*":
return qs.all()
lookup = {qs.model.NATURAL_KEY: param}
return qs.filter(**lookup)

Note: this answer is based on albars answer, but improved for allowing pk params.
manager:
class NexchangeManager(models.Manager):
def get_by_natural_key(self, param):
qs = self.get_queryset()
if param == "*":
return self.all()
lookup = {qs.model.NATURAL_KEY: param}
return self.get(**lookup)
Generic Model class:
class NexchangeModel(models.Model):
class Meta:
abstract = True
objects = NexchangeManager()

Related

How to rearrange priority field for a django model?

I have a Model with a priority field of type postitive integer. This field is unique and allows me to manage the priority of objects.
For example, I want the most important object to have priority one, the second most important to have priority two, etc...
Example:
[
{ "name": "object82",
"priority": 1
}
{ "name": "object54",
"priority": 2
}
{ "name": "object12",
"priority": 3
}
]
class MyObject(models.Model):
name = models.CharField(_("name"), max_length=255)
priority = models.PositiveSmallIntegerField(_("priority"), unique=True)
I want to override the object serializer so that if I add a new object with an existing priority, it unpacks the existing objects. (same thing for the path of an existing object)
For example if I take the example above and add:
{ "name": "object22",
"priority": 2
}
I want the following result:
[
{ "name": "object82",
"priority": 1 // the priority didn't changed
}
{ "name": "object22", // my new object
"priority": 2
}
{ "name": "object54",
"priority": 3 // the priority had changed
}
{ "name": "object12", // the priority had changed
"priority": 4
}
]
I think I have to check first if an object with the same priority exists in the database or not.
If not => I save as is
If yes, I have to change the priority of some objects before add the new object.
How to do this ?
Maybe something like:
class MyObjectSerializer(serializers.ModelSerializer):
class Meta:
model = MyObject
fields = '__all__'
def update(self, instance, validated_data):
target_priority = validated_data.get('priority')
if MyObject.objects.filter(target_priority).exists():
existing_priorities = MyObject.objects.filter(priority__gte=target_priority)
for existing_priority in existing_priorities:
existing_priority.priority += 1
existing_priority.save(update_fields=['priority'])
instance.priority = target_priority
instance.save(update_fields=['priority'])
I was facing a similar problem, and what I have done is that I have a model form and I'm doing the validation in clean function
def clean(self):
cleaned_data = super().clean()
priority = cleaned_data.get('priority')
task = Task.objects.filter(priority__exact=priority)
while task.exists():
prev_task_id = task[0].id
task.update(priority=priority+1)
priority += 1
task = Task.objects.filter(priority__exact=priority).exclude(pk=prev_task_id)
return cleaned_data
I have used the prev_task_id variable for excluding the model that is just got updated. For e.g. let's say we have data
{
title: 'first one',
priority: 3
},
{
title: 'second one',
priority: 4
}
So now if I got priority 3 and after updating it we will have two tasks with priority 4 so we have to exclude the previous task i.e. 'first one'. We have to only update the second task in next iteration
PS:- This code is written assuming that in the database no duplicate priority exists.

Django rest framework - serializer for dictionary structured payload

I'm trying to create a Serializer for a payload that looks something like this -
{
"2fd08845-9b21-4972-87ed-2e7fd03448c5": {
"operation": "Create",
"operationId": "356f6501-a117-4c8d-98ce-dcb4344d481b",
"user": "superuser",
"immediate": "true"
},
"fe6d0c85-0021-431e-9955-e8e1b1ebc414": {
"operation": "Create",
"operationId": "adcedb2f-c751-441f-8108-2c29667ea9cf",
"user": "employee",
"immediate": "false"
}
}
I thought of using DictField, but my problem is that there isn't a field name. it's only a dictionary of keys and values.
I tried something like:
class UserOperationSerializer(serializers.Serializer):
operation = serializers.ChoiceField(choices=["Create", "Delete"])
operationId = serializers.UUIDField()
user = serializers.CharField()
immediate = serializers.BooleanField()
class UserOperationsSerializer(serializers.Serializer):
test = serializers.DictField(child=RelationshipAuthorizeObjectSerializer())
But again, there isn't a 'test' field.
I think your easiest path forward would be to flatten the payload to the following format:
[
{
"request_id": "2fd08845-9b21-4972-87ed-2e7fd03448c5",
"operation": "Create",
"operationId": "356f6501-a117-4c8d-98ce-dcb4344d481b",
"user": "superuser",
"immediate": "true"
},
{
"request_id": "fe6d0c85-0021-431e-9955-e8e1b1ebc414",
"operation": "Create",
"operationId": "adcedb2f-c751-441f-8108-2c29667ea9cf",
"user": "employee",
"immediate": "false"
}
]
And then serialize it. Otherwise, you'd be creating custom fields/serializers which is not pretty.
The way I finally solved it was to add a dynamic 'body' field that contains the real payload of the request.
class UserOperationSerializer(serializers.Serializer):
operation = serializers.ChoiceField(choices=["Create", "Delete"])
operationId = serializers.UUIDField()
user = serializers.CharField()
immediate = serializers.BooleanField()
class UserOperationsSerializer(serializers.Serializer):
body = serializers.DictField(child=UserOperationSerializer())
def __init__(self, *args, **kwargs):
kwargs['data'] = {'body': kwargs['data']}
super().__init__(*args, **kwargs)
Then, in the View, I will use that data as serializer.validated_data['body']
That did the work for me.

Django Rest Framework: Get unique list of values from nested structure

I want to be able to return a list of strings from a deeply nested structure of data. In this scenario, I have a API that manages a chain of bookstores with many locations in different regions.
Currently, I have an API endpoint that takes a region's ID and returns a nested JSON structure of details about the region, the individual bookstores, and the books that can be found in each store.
{
"region": [
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Bar"
}
},
{
"book": {
"name": "Baz"
}
}
],
},
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Bar"
}
}
],
},
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Baz"
}
},
{
"book": {
"name": "Qux"
}
}
]
}
]
}
My models look like the following. I am aware these models don't make the most sense for this contrived example, but it does reflect my real world code:
class Book(TimeStampedModel):
name = models.CharField(default="", max_length=512)
class Bookstore(TimeStampedModel):
value = models.CharField(default="", max_length=1024)
book = models.ForeignKey(Book, on_delete=models.CASCADE)
class Region(TimeStampedModel):
stores = models.ManyToManyField(Bookstore)
class BookstoreChain(TimeStampedModel):
regions = models.ManyToManyField(Region)
The serializers I created for the above response look like:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
class BookstoreSerializer(serializers.ModelSerializer):
books = BookSerializer()
class Meta:
model = Bookstore
fields = "__all__"
class RegionSerializer(serializers.ModelSerializer):
stores = BookstoreSerializer(many=True)
class Meta:
model = Region
fields = "__all__"
class BookstoreChainSerializer(serializers.ModelSerializer):
regions = RegionSerializer(many=True)
class Meta:
model = BookstoreChain
fields = "__all__"
I'm not sure what my view or serializer for this solution need to look like. I'm more familiar with writing raw SQL or using an ORM/Linq to get a set of results.
While the above response is certainty useful, what I really want is an API endpoint to return a unique list of book names that can be found in a given region (Foo, Bar, Baz, Qux). I would hope my response to look like:
{
"books": [
"Foo",
"Bar",
"Baz",
"Qux"
]
}
My feeble attempt so far has a urls.py with the following path:
path("api/regions/<int:pk>/uniqueBooks/", views.UniqueBooksForRegionView.as_view(), name="uniqueBooksForRegion")
My views.py looks like:
class UniqueBooksForRegionView(generics.RetrieveAPIView):
queryset = Regions.objects.all()
serializer_class = ???
So you start from region you have to get the stores, so you can filter the books in the stores, here is a solution which will work.
Note:
Avoid using .get() in *APIView because it will trigger an error if the request does not have the ID, you can use get_object_or_404(), but then you cannot log your error in Sentry.
To get an element from an *APIView, use filter().
import logging as L
class UniqueBooksForRegionView(generics.RetrieveAPIView):
lookup_field = 'pk'
def get(self, *args, **kwargs)
regions = Region.objects.filter(pk=self.kwargs[self.lookup_field])
if regions.exists():
region = regions.first()
stores_qs = region.stores.all()
books_qs = Book.objects.filter(store__in=stores_qs).distinct()
# use your book serializer
serializer = BookSerializer(books_qs, many=True)
return Response(serializer.data, HTTP_200_OK)
else:
L.error(f'Region with id {self.kwargs[self.lookup_field]} not found.')
return Response({'detail':f'Region with id {self.kwargs[self.lookup_field]} not found.'}, HTTP_404_NOT_FOUND)
Note
Here is the flow, the code may need some tweaks, but I hope it helps you understand the flow

Flatten json return by DRF

I have json API returned as below format.
But I want to return json API decomposing namingzone key as specified below.
Could anyone tell me how I can revise serializer to achieve this?
serializer.py is also specified below.
For models.py and views.py, please refer to my previous post.
current
{
"zone": {
"zone": "office_enclosed",
"namingzone": [
{
"naming": "moffice"
}
]
},
"lpd": 11.9,
"sensor": true
},
{
"zone": {
"zone": "office_open",
"namingzone": [
{
"naming": "off"
},
{
"naming": "office"
}
]
},
"lpd": 10.5,
"sensor": true
}
Target
{
"zone": "office_enclosed",
"naming": "moffice",
"lpd": 11.9,
"sensor": true
},
{
"zone": "office_open",
"naming": "off",
"lpd": 10.5,
"sensor": true
},
{
"zone": "office_open",
"naming": "office",
"lpd": 10.5,
"sensor": true
}
serializer.py
class namingNewSerializer(serializers.ModelSerializer):
class Meta:
model=Naming
fields=('naming',)
class zoneSerializer(serializers.ModelSerializer):
namingzone=namingNewSerializer(many=True)
class Meta:
model=Zone
fields = ('zone','namingzone')
class lightSerializer(serializers.ModelSerializer):
zone = zoneSerializer()
class Meta:
model=Light
fields = ('zone','lpd','sensor')
class namingSerializer(serializers.ModelSerializer):
zone=zoneSerializer()
class Meta:
model=Naming
fields=('zone','naming')
I would say using Serializer might complicate the implementations. Rather, you can take an pythonic approach. Try like this:
class SomeView(APIView):
...
def get(self, request, *args, **kwargs):
data = lightSerializer(Light.objects.all(), many=True).data
data = list(data) # convert lazy object to list
updated_data = list()
for item in data:
newdict = dict()
zone = item['zone']
newdict.update({'zone':zone['zone'], 'lpd': item['lpd'], 'sensor':item['sensor']})
for naming_zone in zone.get('namingzone'):
naming_zone.update(newDict)
updated_data.append(naming_zone)
return Response(updated_data, status=status.HTTP_200_OK)
See DRF Field document about source. It will help you.
https://www.django-rest-framework.org/api-guide/fields/#source

Django graphql graphene removing redundant queries from return value

The last few days I have read up on so much graphql that I can't see the trees from the forest anymore.
The results that this person got in the beginning is almost exactly what I want (his problem, not his solution), but it seems that a lot of the code is deprecated and I can't seem to get it working: link
I have a bunch of containers that I return. All the containers have the amounts for every day in them. I only want to return the amounts of a certain day.
At the moment, I do return these results (day), but all the other results (days) also return with a Null value.
Current behavior:
{
"data": {
"listProductcontainers": [
{
"id": "1",
"productid": {
"productid": "CBG2",
"processedstockamountsSet": [
{
"timeStampID": {
"id": "2"
},
"id": "77745",
"prodName": {
"productid": "CBG2"
}
},
{
"timeStampID": null, <--------
"id": "89645",
"prodName": {
"productid": "CBG2"
}
},
{
"timeStampID": null, <--------
"id": "89848",
"prodName": {
"productid": "CBG2"
}
},
// ...
Requested behavior: (All values with 'Null' should not return)
{
"data": {
"listProductcontainers": [
{
"id": "1",
"productid": {
"productid": "CBG2",
"processedstockamountsSet": [
{
"timeStampID": {
"id": "2"
}
My query that I am running looks like this:
query{
listProductcontainers{
id
productid{
productid
processedstockamountsSet{
timeStampID(id:2){
id
}
id
prodName{
productid
}
}
}
}
}
Here are the relevant code for the results:
class TimeStampType(DjangoObjectType):
class Meta:
model = TimeStamp
class ProcessedStockAmountsType(DjangoObjectType):
timeStampID = graphene.Field(TimeStampType, id=graphene.Int())
class Meta:
model = ProcessedStockAmounts
def resolve_timeStampID(self, info, **kwargs):
id = kwargs.get('id')
if self.timeStampID.id == id:
return self.timeStampID
class ProductcontainersType(DjangoObjectType):
class Meta:
model = Productcontainers
class ProductlistType(DjangoObjectType):
class Meta:
model = Productlist
class Query(graphene.ObjectType):
list_productcontainers = graphene.List(ProductcontainersType)
def resolve_list_productcontainers(self, context, **kwargs):
return Productcontainers.objects.all()
I have read almost everything in graphene by now, but if you even have a link that mirrors what I want to do I would really appreciate it.
My final option is to make two calls where I get all the container ids, and another call where I get all the amounts (with container id) for a certain date, and with 2 for loops I just add the amounts into the corresponding container... :(