Django Get content from FK of FK - python-3.x

I'm facing troubles with my django project.
I have a three layer model:
model.py
class PLC(models.Model):
name = models.CharField(max_length=50)
class Measure(models.Model):
class Meta:
unique_together = ('PLC', 'address', 'is_bit')
PLC = models.ForeignKey(PLC, on_delete=models.CASCADE)
address = models.IntegerField()
is_bit = models.BooleanField()
class MeasureValue(models.Model):
measure = models.ForeignKey(Measure, on_delete=models.CASCADE)
value = models.TextField()
In my view.py, I want to be able to get measures values out of the plc
class PLCViewSet(viewsets.ModelViewSet):
queryset = PLC.objects.all()
serializer_class = PLCSerializer
def measures(request, id):
plc = PLC.objects.get(id=id)
measures = Measure.objects.filter(PLC=id)
values = MeasureValue.objects.filter(measure__PLC=id)
context = {
'PLC': plc,
'MEASURES': measures,
'VALUES': values
}
return render(request, 'measures.html', context)
Unfortunately, i cannot make a correct filter() (data are stored in sqlite base).

Have you looked into prefetch_related and Prefetch? Those would allow you to pull the related fields when fetching the PLC instance. Then you can access them as follows:
plc = PLC.objects.prefetch_related('measure_set__measurevalue_set').filter(id=id).first()
plc.measure_set.all() # QuerySet of `Measure` that are filtered to the `plc`
for measure in plc.measure_set.all():
measure.measurevalue_set.all() # QuerySet of `MeasureValue` that are filtered to the `measure` therefore `plc`

Related

Django rest_framework list objects based on geographical proximity using GeoDjango gis.geo

I am working with django rest_framework and im stuck here. Im new to django so please bear with me. Im trying to get the coordinates of my current user, then listing all the studios from closest to farthest away. Im using GeoDjango's Point function to convert the user and studio models longitude and latitude into a Point by defining a property in the models. Then im serializing the property field so my API View can compute distance from the user location to all the studio locations. This is the part where I am stuck at. I get "TypeError: Distance function requires a geometric argument in position 1". I also get "TypeError: Object of type Point is not JSON serializable when I call queryset = Studio.objects.all()"
In summary im trying to list all the studios based on geographical proximity to a the user location and list them in order from closest to farthest away. Pls help
Models
class Studio(models.Model):
name = models.CharField(max_length=120, primary_key=True)
address = models.CharField(max_length=120)
lon = models.FloatField()
lat = models.FloatField()
postal_code = models.CharField(max_length=7)
phone_num = models.CharField(max_length=12)
images = models.ImageField(upload_to='studios')
#property
def studio_loc(self):
studio_location = Point(self.lon, self.lat)
return studio_location
class User(AbstractUser):
first_name = models.CharField(max_length=120, null=True)
last_name = models.CharField(max_length=120, null=True)
email = models.EmailField(max_length=120, unique=True)
avatar = models.ImageField(null=True)
phone_num = models.IntegerField(null=True)
username = None
password = models.CharField(max_length=120)
lon = models.FloatField(null=True)
lat = models.FloatField(null=True)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
#property
def user_loc(self):
user_location = Point(self.lon, self.lat)
return user_location
serializers.py
class UserLocationSerializer(serializers.ModelSerializer):
user_loc = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('email', 'user_loc')
def get_user_loc(self, obj):
point = obj.user_loc()
print(point)
return point
class StudiosForUserSerializer(serializers.Serializer):
studio_loc = serializers.SerializerMethodField()
class Meta:
model = Studio
fields = ('name', 'address', 'postal_code', 'phone_num', 'images', 'studio_loc')
def get_studio_loc(self, obj):
point = obj.studio_loc()
print(point)
return json.dumps(point)
view.py
class StudiosForUserView(ListAPIView):
permission_classes = [IsAuthenticated]
serializer_class = StudiosForUserSerializer
def get_queryset(self):
user_serializer = UserLocationSerializer(self.request.user)
user_location = user_serializer['user_loc']
queryset = Studio.objects.annotate(distance=Distance(self.serializer_class["studio_loc"], user_location)).order_by("distance")[0:3]
#queryset = Studio.objects.all()
print(queryset)
return queryset
urls.py
urlpatterns = [
path('all/', StudiosForUserView.as_view()),
]

Django search with AND operator many to many relationship

This is my model
class MenuItem(models.Model):
name = models.CharField(max_length=500, null=False)
description = models.CharField(max_length=500, null=True)
image_url = models.CharField(max_length=1000, null=True)
menu_category = models.ForeignKey(MenuCategory, on_delete=models.CASCADE)
def __str__(self):
return f'{self.name}'
class Venue(models.Model):
name = models.CharField(max_length=500, null=False)
def __str__(self):
return f'{self.name}'
class VenueMenu(models.Model):
venue = models.ForeignKey(Venue, null=False, on_delete=models.CASCADE)
menu_item = models.ManyToManyField(MenuItem, null=False)
This is my view
#api_view(['GET'])
def search_menu_item(request):
if request.GET.get('venue') and request.GET.get('search_name'):
menu_item_filter = Q(menu_item__name__icontains=request.GET.get('search_name'))
venue_filter = Q(venue__name=request.GET.get('venue').title())
menu_item_search = VenueMenu.objects.filter(venue_filter & menu_item_filter)
serializer = VenueMenuSerializer(menu_item_search, many=True)
return Response(serializer.data)
This is my serializer
class MenuItemSerializer(serializers.ModelSerializer):
menu_category = MenuCategorySerializer(many=True)
class Meta:
model = MenuItem
fields = '__all__'
class VenueSerializer(serializers.ModelSerializer):
class Meta:
model = Venue
fields = '__all__'
class VenueMenuSerializer(serializers.ModelSerializer):
menu_item = MenuItemSerializer(many=True)
venue = VenueSerializer(many=False)
I want to search a single menu item information in a specific venue, as you can see in my query am querying the object but this query is returning me all the menu items associated with that venue including the one which I have searched but I want to have a single menu item which I am searching associated to that venue.
You get the correctly filtered VenueMenus, but the related MenuItems aren't filtered automatically because the filter is for VenueMenu and not MenuItem.
To filter the related MenuItems in the serializer, you'll have to do a filtered prefetch using Prefetch like so:
from django.db.models import Prefetch
menu_item_search = VenueMenu.objects.filter(
venue_filter & menu_item_filter
).prefetch_related(
Prefetch(
'menu_item',
queryset=MenuItem.objects.filter(name__icontains=request.GET.get('search_name'))
)
)
Also to improve this further, you can also select the related Venue using select_related to avoid doing a separate query just to get the venue details in the serializer. So all in all:
menu_item_search = VenueMenu.objects.filter(
venue_filter & menu_item_filter,
).select_related(
'venue',
).prefetch_related(
Prefetch(
'menu_item',
queryset=MenuItem.objects.filter(name__icontains=request.GET.get('search_name'))
)
)

Django RestFramework return just last result after filtered

In my django project i use DjangoRestFramework for expose some API endpoint.
Now i am try to expose just the last result of a model after filtered the data.
Models:
class VarsResults(models.Model):
id = models.AutoField(primary_key=True)
id_res = models.ForeignKey(Results, related_name="mainres", on_delete=models.CASCADE)
var_id = models.ForeignKey(ModbusVariable, null=True, on_delete=models.SET_NULL)
var_val = models.CharField(max_length=400, blank=True)
var_val_conv = models.CharField(max_length=100, blank=True, null=True)
base_byte_order = models.CharField(max_length=15)
var_hash = models.CharField(max_length=400)
serialyzers:
class VarsResultsSerializer(serializers.ModelSerializer):
id_res = ResultsSerializer(read_only=True)
var_id = ModvarSerializer(read_only=True)
class Meta:
model = VarsResults
fields = ['id', 'id_res', 'var_id', 'var_val', 'var_conv', 'var_hash']
views.py
class VarResultsListLast(generics.ListAPIView):
queryset = VarsResults.objects.using(random.choice(replica_list)).order_by('-id')[:1]
serializer_class = VarsResultsSerializer
pagination_class = StandardResultsSetPagination
# paginator = None
filter_backends = [DjangoFilterBackend]
filterset_fields = {
'id_res__id': ['exact'],
'id_res__device': ['exact'],
'id_res__unit__id': ['exact'],
'id_res__proj_code': ['exact'],
'var_id__var_group__short_name': ['exact'],
'id_res__read_date': ['gte', 'lte', 'exact', 'gt', 'lt']
}
search_fields = ['id_res__id', 'id_res__unit__id', 'id_res__device', 'id_res__proj_code', 'id_res__read_date']
ordering_fields = '__all__'
I create my queryset using .order_by('-id')[:1] for have just the last results but it works only if i don't use any filter (return the last result for the whole table), if i try to filter, for example using proj_code or somethin other filter i would to have in return just the last result from the filtered data, but i have:
(call for example: http://127.0.0.1:8000/api/results_data_last/?id_res__proj_code=CR00069)
AssertionError at /api/results_data_last/
Cannot filter a query once a slice has been taken.
How can i execute my filtering options and then have in return just the last record (based on my model id)?
So many thanks in advance
By using .last() you can get the last record. Detailed in the docs
https://docs.djangoproject.com/en/3.2/ref/models/querysets/#last

API to return a subset of fields after joining 2 models

I have 2 models namely,
ModelA:
class ModelA(models.Model):
id = models.IntegerField()
common_field = models.CharField(max_length=20)
model_a_field1 = models.CharField(max_length=20)
model_a_field2 = models.CharField(max_length=10)
model_a_field3 = models.CharField(max_length=20)
model_a_field4 = models.CharField(max_length=10)
model_a_field5 = models.CharField(max_length=20)
class Meta:
db_table = modelA
and ModelB:
class ModelB(models.Model):
id = models.IntegerField()
common_field = models.CharField(max_length=20)
model_b_field1 = models.CharField(max_length=20)
model_b_field2 = models.CharField(max_length=10)
model_b_field3 = models.CharField(max_length=20)
model_b_field4 = models.CharField(max_length=10)
class Meta:
db_table = modelB
I need to create an API such that on accessing the endpoint, I have to return the below fields from both the models which have common_field as the common column linking them:
common_field, model_a_field1, model_a_field2, model_a_field5, model_b_field2, model_b_field3
I know I can do this by doing the below:
Model
class ModelC(models.Model):
common_field = models.CharField(max_length=20)
model_a_field1 = models.CharField(max_length=20)
model_a_field2 = models.CharField(max_length=10)
model_a_field5 = models.CharField(max_length=20)
model_b_field2 = models.CharField(max_length=10)
model_b_field3 = models.CharField(max_length=20)
class Meta:
managed = False
Serializer:
class ModelCSerializer(serializers.ModelSerialaizer):
class Meta:
model = ModelC
fields = '__all__'
Views:
class ModelCList(mixins.ListModelMixin, generics.GenericAPIView):
queryset = ModelC.objects.raw('select modelA.id, modelA.common_field, modelA.model_a_field1, modelA.model_a_field2, modelA.model_a_field5, modelB.model_b_field2, modelB.model_b_field3 from modelA inner join modelB on modelA.common_field = modelB.common_field')
serializer_class = ModelCSerializer
def get(self, request):
return self.list(request)
Is there any other way to achieve this. Especially if there is a way without having to write a query as in the above?
select modelA.id, modelA.common_field, modelA.model_a_field1, modelA.model_a_field2, modelA.model_a_field5, modelB.model_b_field2, modelB.model_b_field3 from modelA inner join modelB on modelA.common_field = modelB.common_field
Have u tried executing this though ? I doubt it works.
same query in django's ORM could look like:
common_field = "blahblah"
queryset_a = ModelA.filter(common_field=common_field)
.values("id", "common_field", "model_a_field1", "model_a_field2", "model_a_field5")
queryset_b = ModelB.filter(common_field=common_field)
.values("model_b_field2, model_b_field3")
from itertools import zip_longest
final = [{**dicta, **dictb} for dicta, dictb in zip_longest(queryset_a, queryset_b, fillvalue={})]]
But this approach has many disadvantages so the point is that if you are doing it this way, there's something wrong with your database design.
which have common_field as the common column linking them
In SQL, if you want to link 2 tables and then join them, you should define a relationship between the 2 tables using a foreign key. Since you have only mentioned very generic models in your question, this is not something I can help you with but I'd recommend you to read about django's Relationships in Models.

Is there a through model in foreignkey in foreignkey like in manytomanyfield?

I'm starting a web-like esports. I have a class called the game and Tournament.
Tournament has a foreignkey with game. Game has objects pubg and clashroyale. When Tournament is created with game pubg it has to have a attribute called "prize_per_kill". but not with other games. It has to be unique to that Tournament when created with that game. How can i do this?
I thought about using manytomanyfield but a single Tournament has only one Game. so it must be foreignkey.
I expect special attributes to that tournament that i can iterate through in detail page.
'''
class Tournament(models.Model):
game = models.ForeignKey(Game, on_delete=models.CASCADE)
players = models.ManyToManyField(User,through="Subscription")
max_players = models.IntegerField(default=100)
tourney_type = models.CharField(max_length=20)
price = models.IntegerField()
time = models.DateTimeField()
tourney_id = models.CharField(max_length=50)
tourney_pass = models.CharField(max_length=15)
first_prize = models.IntegerField()
second_prize = models.IntegerField()
third_prize = models.IntegerField()
'''
'''
class Game(models.Model):
users = models.ManyToManyField(User, through="Membership")
name = models.CharField(max_length=20)
desc = models.TextField()
'''
You can create an abstract Game model, PubgGame and ClashRoyaleGame models that inherit from your abstract model. That will give you two main flexibilities:
As Game will be an abstract model, no table will be created for it. You'll only have tables for PubgGame etc. game models.
You can add specific fields for each game such as prize_per_kill.
But using this solutions will arise a problem; you abstract models can't be used as ForeignKey in other models. So you need to find a way to represent your PubgGame and ClashRoyaleGame models in one ForeignKey field. Django has a special field for that: GenericForeignKey. You need to point to a CoontentType and an PositiveIntegerField to your GenericForeignKey to be able store primary key id's of your game models.
# models.py
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
class Game(models.Model):
users = models.ManyToManyField(User, through="Membership")
name = models.CharField(max_length=20)
desc = models.TextField()
class Meta:
abstract = True
class PubgGame(Game):
prize_per_kill = models.TextField() # I dont know what prize_per_kill means :)
class ClashRoyaleGame(Game):
some_other_field = models.TextField()
class Tournament(models.Model):
players = models.ManyToManyField(User,through="Subscription")
max_players = models.IntegerField(default=100)
tourney_type = models.CharField(max_length=20)
price = models.IntegerField()
time = models.DateTimeField()
tourney_id = models.CharField(max_length=50)
tourney_pass = models.CharField(max_length=15)
first_prize = models.IntegerField()
second_prize = models.IntegerField()
third_prize = models.IntegerField()
# GenericForeignKey related fields
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
game = GenericForeignKey('content_type', 'object_id')
Then you can use these fields like:
>>> from your_ap.models import Tournament, PubgGame
>>> pubg_game = PubgGame.objects.create(prize_per_kill="some value")
>>> tournament = Tournament.objects.create(game=pubg_game) # I'm not setting other fields for simplicity.
>>> tournament.save()
>>> tournament.game_object # This will give you a PubgGame model object

Resources