django queryset limited choice filed by FK - django-filter

I have two models
Notification
Asset
To display data using the django-filter.
Unfortunately, when you create a form in the select-field all the values of the model Asset, and I would just those related to FK model Notification.
def get_queryset(self):
try:
self.filter = NotificationListFilter(
self.request.GET,
queryset = Notification.objects.all()
)
return self.filter
except :
return Notification.objects.order_by('-asset__system')
In some way I can build queryset this to happen?

Related

SQLALchemy update ARRAY column [duplicate]

I'm working on a project using Flask and a PostgreSQL database, with SQLAlchemy.
I have Group objects which have a list of User IDs who are members of the group. For some reason, when I try to add an ID to a group, it will not save properly.
If I try members.append(user_id), it doesn't seem to work at all. However, if I try members += [user_id], the id will show up in the view listing all the groups, but if I restart the server, the added value(s) is (are) not there. The initial values, however, are.
Related code:
Adding group to the database initially:
db = SQLAlchemy(app)
# ...
g = Group(request.form['name'], user_id)
db.session.add(g)
db.session.commit()
The Group class:
from flask.ext.sqlalchemy import SQLAlchemy
from sqlalchemy.dialects.postgresql import ARRAY
class Group(db.Model):
__tablename__ = "groups"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))
leader = db.Column(db.Integer)
# list of the members in the group based on user id
members = db.Column(ARRAY(db.Integer))
def __init__(self, name, leader):
self.name = name
self.leader = leader
self.members = [leader]
def __repr__(self):
return "Name: {}, Leader: {}, Members: {}".format(self.name, self.leader, self.members)
def add_user(self, user_id):
self.members += [user_id]
My test function for updating the Group:
def add_2_to_group():
g = Group.query.all()[0]
g.add_user(2)
db.session.commit()
return redirect(url_for('show_groups'))
Thanks for any help!
As you have mentioned, the ARRAY datatype in sqlalchemy is immutable. This means it isn’t possible to add new data into array once it has been initialised.
To solve this, create class MutableList.
from sqlalchemy.ext.mutable import Mutable
class MutableList(Mutable, list):
def append(self, value):
list.append(self, value)
self.changed()
#classmethod
def coerce(cls, key, value):
if not isinstance(value, MutableList):
if isinstance(value, list):
return MutableList(value)
return Mutable.coerce(key, value)
else:
return value
This snippet allows you to extend a list to add mutability to it. So, now you can use the class above to create a mutable array type like:
class Group(db.Model):
...
members = db.Column(MutableList.as_mutable(ARRAY(db.Integer)))
...
You can use the flag_modified function to mark the property as having changed. In this example, you could change your add_user method to:
from sqlalchemy.orm.attributes import flag_modified
# ~~~
def add_user(self, user_id):
self.members += [user_id]
flag_modified(self, 'members')
To anyone in the future: so it turns out that arrays through SQLAlchemy are immutable. So, once they're initialized in the database, they can't change size. There's probably a way to do this, but there are better ways to do what we're trying to do.
This is a hacky solution, but what you can do is:
Store the existing array temporarily
Set the column value to None
Set the column value to the existing temporary array
For example:
g = Group.query.all()[0]
temp_array = g.members
g.members = None
db.session.commit()
db.session.refresh(g)
g.members = temp_array
db.session.commit()
In my case it was solved by using the new reference for storing a object variable and assiging that new created variable in object variable.so, Instead of updating the existing objects variable it will create a new reference address which reflect the changes.
Here in Model,
Table: question
optional_id = sa.Column(sa.ARRAY(sa.Integer), nullable=True)
In views,
option_list=list(question.optional_id if question.optional_id else [])
if option_list:
question.optional_id.clear()
option_list.append(obj.id)
question.optional_id=option_list
else:
question.optional_id=[obj.id]

Is there a way to set the id of an existing instance as the value of a nested serializer in DRF?

I'm developing a chat application. I have a serializer like this:
class PersonalChatRoomSerializer(serializers.ModelSerializer):
class Meta:
model = PersonalChatRoom
fields = '__all__'
user_1 = UserSerializer(read_only=True)
user_2 = UserSerializer()
the user_1 field is auto-populated but the client should provide the user_2 field in order to create a personal chat room with another user.
My problem is, when creating a new chat room, the serializer tries to create a new user object from the input data thus giving me validation errors. What I really want it to do is to accept a user id and set the value of user_2 field to an existing user instance that is currently available in the database and if the user is not found, simply return a validation error. (the exact behavior of PrimaryKeyRelatedField when creating a new object)
I want my input data to look like this:
{
'user_2': 1 // id of the user
}
And when I retrieve my PersonalChatRoom object, I want the serialized form of the user object for my user_2 field:
{
...,
'user_2': {
'username': ...,
'the_rest_of_the_fields': ...
}
}
How can I achieve this?
views.py
class GroupChatRoomViewSet(viewsets.ModelViewSet):
permission_classes = [IsUserVerified, IsGroupOrIsAdminOrReadOnly]
serializer_class = GroupChatRoomSerializer
def get_queryset(self):
return self.request.user.group_chat_rooms.all()
def perform_create(self, serializer):
return serializer.save(owner=self.request.user)
I finally figured out how to do it. I just needed to override the to_representation method and serialize the object there. Here is the code I ended up with:
class PersonalChatRoomSerializer(serializers.ModelSerializer):
class Meta:
model = PersonalChatRoom
fields = '__all__'
read_only_fields = ['user_1']
def to_representation(self, chat_room):
""" Serialize user instances when outputing the results """
obj = super().to_representation(chat_room)
for field in obj.keys():
if field.startswith('user_'):
obj[field] = UserSerializer(User.objects.get(pk=obj[field])).data
return obj

How to fetch latest entry in a related table while making sure the query is optimized

I have two models in consideration. RV_Offers and RV_Details. Each offer can have multiple details i.e. I have a foreignkey relationship field in RV_Details table.
Here is my view:
rv_offers_queryset = RV_Offers.objects.all().select_related('vendor').prefetch_related('details')
details_queryset = RV_Details.objects.all().select_related('rv_offer')
title = Subquery(details_queryset.filter(
rv_offer=OuterRef("id"),
).order_by("-created_at").values("original_title")[:1])
offers_queryset = rv_offers_queryset.annotate(
title=title).filter(django_query)
offers = RVOffersSerializer(offers_queryset, many=True).data
return Response({'result': offers}, status=HTTP_200_OK)
As can be seen, I am passing the offers queryset to the serializer.
Now, here is my serializer:
class RVOffersSerializer(serializers.ModelSerializer):
details = serializers.SerializerMethodField()
vendor = VendorSerializer()
def get_details(self, obj):
queryset = RV_Details.objects.all().select_related('rv_offer')
queryset = queryset.filter(rv_offer=obj.id).latest('created_at')
return RVDetailsSerializer(queryset).data
class Meta:
model = RV_Offers
fields = '__all__'
If you look at the get_details method, I am trying to fetch the latest detail that belongs to an offer. My problem is, even though I am using select_related to optimize the query, the results are still extremely slow, In fact, I am using django debug toolbar to inspect the query and apparently select_related seems to have no effect.
What am I doing wrong or how else can I approach this problem?
This is what I did to reduce the number of queries being hit on the db:
def get_details(self, obj):
details = obj.details.last()
return RVDetailsSerializer(details).data
I was able to reduce the number of queries from 45 to 4 using this.
This is because in the view, I had already used select_related to make the queryset, which in turn is being used here using obj.

Optimization Django ORM

I'm running my own smart house project, using django backend with MySql on raspberry Pi.I've got SensorsData table in DB with thousands records with data from sensors. In my REST API I'm using view which looks like this:
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def list_of_sensors_data(request, format=None):
"""
Get list of all sensors data, only for authenticated users
:param request: GET
:return: list of all sensors data if ok http 200 response
"""
sensors_data = SensorsData.objects.all()
serializer = SensorsDataSerializer(sensors_data, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
I've run perfomance test with locust simulating 10 users trying to use my endpoints. After some time, Django keeps returning 504 Timeout using this particular endpoint. My quest is, how can I optimize this queryset? I need to make it faster.
EDIT SensorsData model:
class SensorsData(models.Model):
sensor = models.ForeignKey(Sensors, on_delete=models.CASCADE)
delivery_time = models.DateTimeField(auto_now_add=True)
sensor_data = models.CharField(max_length=20)
class Meta:
verbose_name = "Sensor data"
verbose_name_plural = "Sensors data"
def __str__(self):
return f"{self.sensor.id}: {self.sensor.name}"
SensorsData Serializer:
class SensorsDataSerializer(serializers.ModelSerializer):
sensor = serializers.SlugRelatedField(read_only=False, many=False, slug_field='name', queryset=Sensors.objects.all())
class Meta:
model = SensorsData
fields = ("sensor", "delivery_time", "sensor_data")
This will introduce an N+1 problem, for each SensorsData object, you will make an additional query to fetch the related Sensor object. The good news is that you can use .select_related(…) [Django-doc] to let Django retrieve all related sensors in the same query:
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def list_of_sensors_data(request, format=None):
sensors_data = SensorsData.objects.select_related('sensor')
serializer = SensorsDataSerializer(sensors_data, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)

Unique validator in WTForms with SQLAlchemy models

I defined some WTForms forms in an application that uses SQLALchemy to manage database operations.
For example, a form for managing Categories:
class CategoryForm(Form):
name = TextField(u'name', [validators.Required()])
And here's the corresponding SQLAlchemy model:
class Category(Base):
__tablename__= 'category'
id = Column(Integer, primary_key=True)
name = Column(Unicode(255))
def __repr__(self):
return '<Category %i>'% self.id
def __unicode__(self):
return self.name
I would like to add a unique constraint on the form validation (not on the model itself).
Reading the WTForms documentation, I found a way to do it with a simple class:
class Unique(object):
""" validator that checks field uniqueness """
def __init__(self, model, field, message=None):
self.model = model
self.field = field
if not message:
message = u'this element already exists'
self.message = message
def __call__(self, form, field):
check = self.model.query.filter(self.field == field.data).first()
if check:
raise ValidationError(self.message)
Now I can add that validator to the CategoryForm like this:
name = TextField(u'name', [validators.Required(), Unique(Category, Category.name)])
This check works great when the user tries to add a category that already exists \o/
BUT it won't work when the user tries to update an existing category (without changing the name attribute).
When you want to update an existing category : you'll instantiate the form with the category attribute to edit:
def category_update(category_id):
""" update the given category """
category = Category.query.get(category_id)
form = CategoryForm(request.form, category)
The main problem is I don't know how to access the existing category object in the validator which would let me exclude the edited object from the query.
Is there a way to do it? Thanks.
In the validation phase, you will have access to all the fields. So the trick here is to pass in the primary key into your edit form, e.g.
class CategoryEditForm(CategoryForm):
id = IntegerField(widget=HiddenInput())
Then, in the Unique validator, change the if-condition to:
check = self.model.query.filter(self.field == field.data).first()
if 'id' in form:
id = form.id.data
else:
id = None
if check and (id is None or id != check.id):
Although this is not a direct answer I am adding it because this question is flirting with being an XY Problem. WTForms primary job is to validate that the content of a form submission. While a decent case could be made that verifying that a field's uniqueness could be considered the responsibility of the form validator, a better case could be made that this is the responsibility of the storage engine.
In cases where I have be presented with this problem I have treated uniqueness as an optimistic case, allowed it to pass form submission and fail on a database constraint. I then catch the failure and add the error to the form.
The advantages are several. First it greatly simplifies your WTForms code because you do not have to write complex validation schemes. Secondly, it could improve your application's performance. This is because you do not have to dispatch a SELECT before you attempt to INSERT effectively doubling your database traffic.
The unique validator needs to use the new and the old data to compare first before checking if the data is unique.
class Unique(object):
...
def __call__(self, form, field):
if field.object_data == field.data:
return
check = DBSession.query(model).filter(field == data).first()
if check:
raise ValidationError(self.message)
Additionally, you may want to squash nulls too. Depending on if your truly unique or unique but allow nulls.
I use WTForms 1.0.5 and SQLAlchemy 0.9.1.
Declaration
from wtforms.validators import ValidationError
class Unique(object):
def __init__(self, model=None, pk="id", get_session=None, message=None,ignoreif=None):
self.pk = pk
self.model = model
self.message = message
self.get_session = get_session
self.ignoreif = ignoreif
if not self.ignoreif:
self.ignoreif = lambda field: not field.data
#property
def query(self):
self._check_for_session(self.model)
if self.get_session:
return self.get_session().query(self.model)
elif hasattr(self.model, 'query'):
return getattr(self.model, 'query')
else:
raise Exception(
'Validator requires either get_session or Flask-SQLAlchemy'
' styled query parameter'
)
def _check_for_session(self, model):
if not hasattr(model, 'query') and not self.get_session:
raise Exception('Could not obtain SQLAlchemy session.')
def __call__(self, form, field):
if self.ignoreif(field):
return True
query = self.query
query = query.filter(getattr(self.model,field.id)== form[field.id].data)
if form[self.pk].data:
query = query.filter(getattr(self.model,self.pk)!=form[self.pk].data)
obj = query.first()
if obj:
if self.message is None:
self.message = field.gettext(u'Already exists.')
raise ValidationError(self.message)
To use it
class ProductForm(Form):
id = HiddenField()
code = TextField("Code",validators=[DataRequired()],render_kw={"required": "required"})
name = TextField("Name",validators=[DataRequired()],render_kw={"required": "required"})
barcode = TextField("Barcode",
validators=[Unique(model= Product, get_session=lambda : db)],
render_kw={})
Looks like what you are looking for can easily be achieved with ModelForm which is built to handle forms that are strongly coupled with models (the category model in your case).
To use it:
...
from wtforms_components import Unique
from wtforms_alchemy import ModelForm
class CategoryForm(ModelForm):
name = TextField(u'name', [validators.Required(), Unique(Category, Category.name)])
It will verify unique values while considering the current value in the model. You can use the original Unique validator with it.
This worked for me, simple and easy:
Make sure that every time when a new row created in DB it must have unique name in colomn_name_in_db otherwise it will not work.
class SomeForm(FlaskForm):
id = IntegerField(widget=HiddenInput())
fieldname = StringField('Field name', validators=[DataRequired()])
...
def validate_fieldname(self, fieldname):
names_in_db = dict(Model.query.with_entities(Model.id,
Model.colomn_name_in_db).filter_by(some_filtes_if_needed).all())
if fieldname.data in names_in_db.values() and names_in_db[int(self.id)] != fieldname.data:
raise ValidationError('Name must be unique')