django manytomanyfield filter - django

so I am building an app with django django rest and I have some issues with many to many fields in my models,
in the app there are two models that are connected by a many to many field option and version. the problem is that I want to filter my option and get only the option related to a number of version and only those who have (Default True).
here is my models
class Option(models.Model):
Code_Option = models.CharField(max_length=20, primary_key=True)
Nom_Option = models.CharField(max_length=100)
option_Version = models.ManyToManyField(Version,through='Option_Version')
class Option_Version(models.Model):
option = models.ForeignKey(Option, on_delete=models.CASCADE)
version = models.ForeignKey(Version, on_delete=models.CASCADE)
Default = models.BooleanField(default = False)
class Version(models.Model):
Code_Version = models.CharField(max_length=20, primary_key=True)
Nom_Version = models.CharField(max_length=200)
Id_Modele = models.ForeignKey(Modele, on_delete=models.CASCADE, related_name='Version_set')
and here is what I tried to do
class Option_defaut_Version(ListAPIView):
serializer_class = Option_Sereializer
def get_queryset(self):
id_version = self.kwargs['Id_Version']
//id version refers to Code_Version from Version (it's the PK) and I get it from the url ex: option/default/<srt:Id_Version>
return Option.objects.filter(option_Version__version = id_version).filter(option_Version__default = "True")
and here is my sereilasers :
class Option_Sereializer(serializers.ModelSerializer):
class Meta:
model = Option
fields = [
'Code_Option',
'Nom_Option',
'option_Version'
]
class Option_Version_Sereializer(serializers.ModelSerializer):
class Meta:
model = Option_Version
fields = [
'option',
'version',
'Default'
]
class Version_Sereializer(serializers.ModelSerializer):
class Meta:
model = Version
fields = [
'Nom_Version',
'Code_Version'
]
also I tried this:
class option_version_default(ListAPIView):
serializer_class = Option_Version_Sereializer
def get_queryset(self):
return Option_Version.objects.all()
the result was an error:
AttributeError at /option/default/v1
type object 'Option_Version' has no attribute 'objects'
if anyone could help me I would apreciate it
ps: I saw a lot of other questions and answers also the doc from django django rest but it didn't help me

Related

Django rest framework serializer for membership Model?

How do I use Serializer for something like This
class Language(BaseModel):
name = models.CharField(null=False, blank=False, unique=True, max_length=150)
class Helper(BaseModel):
languages = models.ManyToManyField('Language', blank=True, through="HelperLanguage")
class HelperLanguage(BaseModel):
helper = models.ForeignKey('Helper', on_delete=models.CASCADE)
language = models.ForeignKey('Language', on_delete=models.CASCADE)
read = models.BooleanField()
write = models.BooleanField()
speak = models.BooleanField(default=True)
class LanguageSerializer(ModelSerializer):
class Meta:
model = Language
fields = ["id", "name"]
class HelperLanguageSerializer(ModelSerializer):
language = LanguageSerializer(read_only=True)
class Meta:
model = HelperLanguage
fields = ["id", "language", "read", "write", "speak"]
class HelperPublicSerializer(ModelSerializer):
languages = HelperLanguageSerializer(read_only=True, many=True)
class Meta:
model = Helper
fields = ['id', 'languages']
while using HelperPublicSerialiser for list view I am getting error
Got AttributeError when attempting to get a value for field read on serializer HelperLanguageSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Language instance.
I do understand the problem but couldn't find any solution probably not using membership model right way.

How to return data from two different tables in django?

I have two models in my models.py. I need to return a json response which includes data from two tables.
How should my view and serializer look like?
class Device(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
device_name = models.CharField(max_length=200, null=True, blank=True )
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.device_name)
class StatusActivity(models.Model):
OFFLINE = 1
ONLINE = 2
STATUS = (
(OFFLINE, ('Offline')),
(ONLINE, ('Online')),
)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
device_id = models.ForeignKey(Device, related_name='StatusActivity', on_delete=models.CASCADE)
changed_to = models.PositiveSmallIntegerField(choices=STATUS)
modified_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.device_id)
Expected Response:
{
"device_id":"",
"device_name":"",
"changed_to":"",
"modified_at":"",
}
UPDATE:
I set my views.py and serializer.py as below. I am checking
Serializer.py
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = '__all__'
class StatusActivitySerializer(serializers.ModelSerializer):
class Meta:
model = StatusActivity
fields = '__all__'
class ListSerializer(serializers.Serializer):
# devices = DeviceSerializer(many=True)
# activities = StatusActivitySerializer(many=True)
class Meta:
model = [Device, StatusActivity]
fields = ['device_id', 'device_name', 'changed_to', 'modified_at']
Views.py
class DeviceListView(generics.ListAPIView):
queryset = Device.objects.all()
serializer_class = ListSerializer
class StatusActivityListView(generics.ListAPIView):
queryset = StatusActivity.objects.all()
serializer_class = StatusActivitySerializer
Actually you don't need to have two separated views for this, because you can easily serialize relations from one serializer class.
Take a look at this useful answer: How do I include related model fields using Django Rest Framework?
For your case you can write something like this:
class StatusActivitySerializer(serializers.ModelSerializer):
device_name = serializers.CharField(source='device_id.device_name')
class Meta:
model = StatusActivity
fields = ('changed_to', 'modified_at', 'device_id', 'device_name')
Something that worth to note:
it's a good idea for ForeignKey field use device name instead of
device_id;
related_name arg should have a name for reverse access. Keep it
meaningful, e.g. status_activities is a good choice.
filter the status activity. you can refer the foreignkey.
eg:
items = []
for statusact in StatusActivity.objects.all():
items.append({
"device_id":statusact.device_id.id,
"device_name":statusact.device_id.device_name,
"changed_to":statusact.changed_to,
"modified_at":statusact.modified_at,
})

How can I make a writable ManyToManyField with a Through Model in Django Rest Framework?

I have a Product class that has "source products"; I use a many-to-many field with a through model to represent it (the database tables already exist, and that's the only way I found to configure the models):
class Product(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
product_name = models.TextField()
source_products = models.ManyToManyField('self', symmetrical=False, related_name='derived_products', through='Link', through_fields=('product', 'source'),)
class Meta:
db_table = 'product'
managed = False
class Link(models.Model):
product = models.ForeignKey('Product', models.CASCADE, db_column='uuid', related_name='source_links')
source = models.ForeignKey('Product', models.CASCADE, db_column='source_uuid', related_name='+')
class Meta:
db_table = 'link'
unique_together = (('product', 'source'),)
managed = False
My serializer is dead simple:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('uuid', 'product_name', 'source_products', )
A GET request returns:
{
"product_name": "dummy.fra",
"source_products": [
"17b021e7-3d6b-4d29-a80b-895d62710080"
],
"uuid": "48c5a344-877e-4e3f-9a4b-2daa136b68fe"
}
The since ManyToManyFields with a Through Model are read-only, how do I go about to create a product including the link between products? I'd like to send a POST request that follows the same format as the GET response (i.e., a source_products field that lists UUIDs of existing products).
You can use PrimaryKeyRelatedField but you have to write custom create method.
class ProductSerializer(serializers.ModelSerializer):
source_products = serializers.PrimaryKeyRelatedField(many=True, queryset=Products.objects.all())
class Meta:
model = Product
fields = ('uuid', 'product_name', 'source_products', )
def create(self, validated_data):
source_products = validated_data.pop('source_products', [])
product = Product.objects.create(**validated_data)
for source in source_products:
product.source_products.add(source)
return product
Your data will be like this
{
"product_name": "dummy.fra",
"source_products": [
"17b021e7-3d6b-4d29-a80b-895d62710080"
],
"uuid": "48c5a344-877e-4e3f-9a4b-2daa136b68fe"
}
The serializer has to override create() and access the unvalidated GET parameters (self.context['request'].data) directly:
class ProductSerializer(serializers.ModelSerializer):
def create(self, validated_data):
# extract sources data
unvalidated_data = self.context['request'].data
sources_data = unvalidated_data.get('source_products', [])
# save objects
instance = Product.objects.create(**validated_data)
for uuid in sources_data:
source = Product.objects.get(pk=uuid)
instance.source_links.create(source=source)
return instance
class Meta:
model = Product
fields = ('uuid', 'product_name', 'source_products', )

Django REST framework, dealing with related fields on creating records

Preliminary note: this is a rather newbie question, though I have not found a sufficient answer on StackOverflow; many similar questions, but not this one. So I am asking a new question.
The problem: I'm having difficulty creating records where one field is a foreign key to an existing record, and I do not know what I'm doing wrong in my code.
In my app there are two models in question, a one-to-many relationship between Company and BalanceSheet:
models:
class Company(models.Model):
cik = models.IntegerField(default=0, unique=True)
symbol = models.CharField(max_length=4, unique=True)
name = models.CharField(max_length=255, unique=True)
def __str__(self):
return self.symbol
class BalanceSheet(models.Model):
company = models.ForeignKey(Company,
null=True,
on_delete=models.CASCADE,
related_name='balance_sheets',)
date = models.DateField()
profit = models.BigIntegerField()
loss = models.BigIntegerField()
class Meta:
unique_together = (('company', 'date'),)
def __str__(self):
return '%s - %s' % (self.company, self.date)
serializers:
class BalanceSheetSerializer(serializers.ModelSerializer):
company = serializers.StringRelatedField()
class Meta:
model = BalanceSheet
fields = ('company','date','profit','loss')
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('cik', 'symbol', 'name')
Views:
class BalanceSheetCreate(generics.CreateAPIView):
model = BalanceSheet
queryset = BalanceSheet.objects.all()
serializer_class = BalanceSheetSerializer
urls include:
url(r'^(?P<symbol>[A-Z]{1,4})/create-balance-sheet/$', views.BalanceSheetCreate.as_view(),
name='create_balance_sheet'),
To this point, I have zero problem reading data. However, when trying to create records, I get errors I don't understand:
curl http://localhost:8000/financials/AAPL/create-balance-sheet/ -X POST -d "company=AAPL&date=1968-04-17&profit=1&loss=1"
IntegrityError at /financials/AAPL/create-balance-sheet/
null value in column "company_id" violates not-null constraint
Dropping the company data from that curl command results in the same error.
How do I get around this error? I thought I was telling the api what company I'm interested in, both explicitly in the url and in the post data.
Using python3.6, django 1.11, and djangorestframework 3.7.7
You get the IntegrityError because your code will try to create a new BalanceSheet without a company. That's because StringRelatedField is read-only (see docs) and therefore it's not parsed when BalanceSheetSerializer is used in write mode.
SlugRelatedField is what you need here:
class BalanceSheetSerializer(serializers.ModelSerializer):
company = serializers.SlugRelatedField(slug_field='symbol')
class Meta:
model = BalanceSheet
fields = ('company','date','profit','loss')
To answer my own question, here's what I wound up with. Thanks again go to dukebody.
models:
class Company(models.Model):
cik = models.IntegerField(default=0)
symbol = models.CharField(max_length=4)
name = models.CharField(max_length=255)
def __str__(self):
return self.symbol
class BalanceSheet(models.Model):
company = models.ForeignKey(Company,
null=True,
on_delete=models.CASCADE,
related_name='balance_sheets',)
date = models.DateField()
profit = models.BigIntegerField()
loss = models.BigIntegerField()
class Meta:
unique_together = (('company', 'date'),)
def __str__(self):
return '%s - %s' % (self.company, self.date)
serializers:
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('cik', 'symbol', 'name')
class BalanceSheetSerializer(serializers.ModelSerializer):
company = CompanySerializer(many=False)
class Meta:
model = BalanceSheet
fields = ('company', 'date', 'profit', 'loss')
def create(self, validated_data):
company_data = validated_data['company']
company, created = Company.objects.get_or_create(**company_data)
validated_data['company'] = company
sheet = BalanceSheet.objects.create(**validated_data)
return sheet
I also had to include the full company data within my curl statement as a nested dict.

django rest framework limit_choices_to attribute ignored?

I'm using the DjangoRest Framework with 2 models DeviceType and Channel. Now Channel has a ForeignKeyField pointing to a DeviceType. No problem so far.
But now I don't want all DeviceTypes to be selectable when adding or editing a Channel but only the DeviceTypes that have their usesChannels field set to True.
So I used the limit_Choices_to attribute but somehow that doesn't seem to work. No matter what I do, I alway get a list with all DeviceTypes including the ones with usesChannels set to False
This is my code:
models.py
class DeviceType(models.Model):
name = models.CharField(max_length=30)
usesChannels = models.BooleanField()
def __str__(self):
return '%s' % (self.name)
class Channel(models.Model):
type = models.ForeignKey(DeviceType, limit_choices_to={'usesChannels': True})
name = models.CharField(max_length=30)
channelNr = models.IntegerField()
serializers.py
class DeviceTypeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = DeviceType
fields = ('url', 'name', 'usesChannels')
class ChannelSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Channel
flieds = ('url', 'type', 'name', 'channelNr')
I used the ForeignKey.limit_choices_to example from This link
Edit: I use the DRF browsable API to add, edit and remove data.
Answer: After struggling with this for a few days I found a working solution:
models.py
class DeviceType(models.Model):
name = models.CharField(max_length=30)
usesChannels = models.BooleanField()
def __str__(self):
return '%s' % (self.name)
class Channel(models.Model):
type = models.ForeignKey(DeviceType)
name = models.CharField(max_length=30)
channelNr = models.IntegerField()
serializers.py
class DeviceTypeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = DeviceType
fields = ('url', 'name', 'usesChannels')
class ChannelSerializer(serializers.HyperlinkedModelSerializer):
type = serializers.PrimaryKeyRelatedField(queryset=DeviceType.objects.filter(usesChannels=True))
class Meta:
model = Channel
flieds = ('url', 'type', 'name', 'channelNr')
Edit: For completeness I'll add the views too
Views.py:
class DeviceTypeViewSet(viewsets.ModelViewSet):
queryset = DeviceType.objects.all()
serializer_class = DeviceTypeSerializer
class ChannelViewSet(viewsets.ModelViewSet):
queryset = Channel.objects.all()
serializer_class = ChannelSerializer
Ran into this problem today. I believe it does indeed completely ignore the attribute; at least I couldn't find any reference to it in DRF's code.
So I "solved" the problem by adding Serializer.__init__ constructor and within it the following function / code:
class MySerializer(Serializers.Serializer):
def __init__(self, *args, **kwargs):
...
def limit_choices_to(field_name):
fld = self.fields[field_name]
fld.queryset = fld.queryset.filter(**Flight._meta.get_field(field_name).get_limit_choices_to())
limit_choices_to('my_field')