I have two models: CustomUser and AgreementReglament
At this moment relation between the models looks like this:
class AgreementReglament(models.Model):
name = models.CharField(max_length=32) # just name of the reglament
approvers = models.ManyToManyField( # set of users to approve
'CustomUser',
related_name='agreement_reglaments'
)
How can I set order of approvers (e.g. CustomUser1 should be the first in particular AgreementReglament1, CustomUser2 should be second and so on, but in AgreementReglament2 CustomUser2 can be the first)
UPDATE: ordering is based on the POST request data
Example of post request to create AgreementReglament:
{
"name": "Reglament1",
"approvers": [
{
"approver_name": "Alex",
"approver_order": 1,
},
{
"approver_name": "Bob",
"approver_order": 2,
}
]
}
UPDATE2
class AgreementReglament(models.Model):
name = models.CharField(max_length=32) # name of the reglament
approvers = models.ManyToManyField( # set of users to approve
'CustomUser',
related_name='agreement_reglaments',
through='Approval'
)
class Approval(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
agreement = models.ForeignKey(
AgreementReglament,
on_delete=models.SET_NULL,
blank=True,
null=True
)
order = models.IntegerField()
If you mean the approver order value will be entered as a field , then I would create a model lets say For example Approvals and it will have a FK field for User and a FK field for Agreement , and a third field for the approver_order. And leave the Agreement Model to have only name field.
But , If you mean to sort based on a condition , the question needs more details to be clarified as (what the ordering is based on ? What is the field you’re ordering exactly ?)
UPDATE:
After seeing your update what I understood is that you get this ordering from the POST request data and for the example you provided, you definitely need a field to store the order for each User.
I suggest going with the same answer, create a model in the middle and link both models with that Model using the through attribute and that might look like this:
class Approval(models.Modal):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
agreement = models.ForeignKey(AgreementReglament, ...)
order = models.#whatever you want this char or int
and in one of the other models (doesn't really matter) they should be like this:
class AgreementReglament(models.Model):
name = models.CharField(max_length=32) # just name of the reglament
approvers = models.ManyToManyField( # set of users to approve
'CustomUser',
related_name='agreement_reglaments', through='Approval'
)
If you have any questions, feel free to ask.
Related
currently I have terms on my applications and one user can have a lot of terms registered, and my current model is like this
class Term(models.Model):
id = models.UUIDField("id", default=uuid.uuid4, editable=False, primary_key=True)
user_id = models.PositiveIntegerField("user id", default=None)
name = models.CharField()
sometimes I need to do a query to get all the users who have terms registered, so I do the following query:
Term.objects.filter(active=True)
.order_by("user_id")
.values("user_id")
.distinct()
and this is enough to solve my problems, but now I'll change my model and it will look like this:
class Term(models.Model):
id = models.UUIDField("id", default=uuid.uuid4, editable=False, primary_key=True)
user_id = models.PositiveIntegerField("user id", default=None)
name = models.CharField()
shared_with = ArrayField(models.PositiveIntegerField("id do usuario"), blank=True) # New
How you can see, I've added a new field named shared_with, that basically is a array of user ids which I want to share terms, So now I need to make a query who will return all ids who can have terms registered (shared_with included). So if i register a Term with user_id = 1 and shared_with = [2,3], my query need to return [1,2,3].
I've solved this problem today with the following code, but I think I can do this just using django ORM and one query:
users = set()
for user in (
Term.objects.filter(active=True)
.order_by("user_id")
.values("user_id")
.distinct()
):
users.add(user["user_id"])
for user in (
Term.objects.filter(active=True)
.order_by("user_id")
.values("shared_with")
):
for user_id in user["shared_with"]:
users.add(user_id)
print(users) # {1,2,3}
If someone knows how to do it and can share the knowledge, I will be grateful.
I don't recommend using the PositiveIntegerField and ArrayField as relations between tables, you can use ForeignKey and ManyToManyField instead, in your case what I understand is a user can have many Terms and a Term can be shared among many users, so the perfect solution is to add ManyToManyField in your User model
class User(AbstarctUser):
... (your fields)
terms = models.ManyToManyField(Term, related_name="users")
and Term model will be like:
class Term(models.Model):
id = models.UUIDField("id", default=uuid.uuid4, editable=False, primary_key=True)
name = models.CharField(max_length=200)
active = models.BooleanField(default=True)
... (other fields)
in that case, if you want to extract user ids with active terms, you can get it as following :
users = User.objects.filter(terms__active=True).distinct().values_list("id", flat=True)
I have the following models:
# Get or create a 'Not selected' category
def get_placeholder_categoy():
category, _ = ListingCategories.objects.get_or_create(category='Not selected')
return category
# Get default's category ID
def get_placeholder_category_id():
return get_placeholder_categoy().id
class ListingCategories(models.Model):
category = models.CharField(max_length=128, unique=True)
def __str__(self):
return f'{self.category}'
class Listing(models.Model):
title = models.CharField(max_length=256)
seller = models.ForeignKey(User, on_delete=models.CASCADE, related_name='listings')
description = models.TextField(max_length=5120, blank=True)
img_url = models.URLField(default='https://media.istockphoto.com/vectors/no-image-available-picture-coming-soon-missing-photo-image-vector-id1379257950?b=1&k=20&m=1379257950&s=170667a&w=0&h=RyBlzT5Jt2U87CNkopCku3Use3c_3bsKS3yj6InGx1I=')
category = models.ForeignKey(ListingCategories, on_delete=models.CASCADE, default=get_placeholder_category_id, related_name='listings')
creation_date = models.DateTimeField()
base_price = models.DecimalField(max_digits=10, decimal_places=2, validators=[
MinValueValidator(0.01),
MaxValueValidator(99999999.99)
])
With these, I have the following form:
class ListingForm(ModelForm):
class Meta:
model = Listing
exclude = ['seller', 'creation_date']
widgets = {
'title': TextInput(attrs=base_html_classes),
'description': Textarea(attrs=base_html_classes),
'img_url': URLInput(attrs=base_html_classes),
'category': Select(attrs=base_html_classes),
'base_price': NumberInput(attrs=base_html_classes)
}
One of the available categories I have is "Not selected", since I want to allow that if at some point a category were to be removed, items can be reassigned to that one, however, when rendering the form, I will do some validation on the view function to prevent it from being submitted if the "not selected" category is sent with the form.
Because of this, I want the HTML form on the template to assign the 'disabled' attribute to the option corresponding to that category, however, I have been searching for a couple of days now without finding anything that I was able to understand to the point where I could try it.
Ideally, another thing I'd like to achieve is to be able to modify the order of the rendered options on the form so that I can move to the top 'not selected' regardless of its primary key within the model.
I am aware I can just create a form instead of a model form, or just modify the template so I manually specify how to render the form itself, but I do feel like there is a simple fix to this either on the model or on the model form that I am just not finding yet.
Thanks in advance!
I would suggest you use (in model definition)
class Listing(models.Model):
..
category = model.ForeignKey(ListingCategories, on_delete=models.SET_NULL, null=True, related_name='listings')
..
and optionally in form definition
class ListingForm(ModelForm):
category = forms.ModelChoiceField(ListingCategories, empty_label='Not Selected')
..
While rendering model form, a required attribute will be automatically added, and in form validating, it is also required. It is only in database validation that the field can be left NULL
Lets say I have the following models:
class Author(models.Model):
first_name = models.CharField(max_length=32)
last_name = models.CharField(max_length=32)
class Book(models.Model):
title = models.CharField(max_length=64)
author = models.ForeignKeyField(Author, on_delete=models.CASCADE)
And I have the following serializer:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('id', 'title', 'author')
read_only_fields = ('id')
If I then query my books, A book's data looks like:
{
"id": 1,
"title": "Book Title",
"author": 4
}
Which is what I want, as I return both an array of books, as well as an array of authors, and allow the client to join everything up. This is because I have many authors that are repeated across books.
However, I want to allow the client to either submit an existing author id to create a new book, or all of the data for a new author. E.g.:
Payload for new book with existing author:
{
"title": "New Book!",
"author": 7
}
or, payload for a new book with a new author:
{
"title": "New Book!",
"author": {
"first_name": "New",
"last_name": "Author"
}
}
However the second version, will not pass the data validation step in my serializer. Is there a way to override the validation step, to allow either an author id, or a full object? Then in my serializer's create method, I can check the type, and either create a new author, get its id, and create the new book, or just attach the existing id. Thoughts?
I believe that it is not possible to do it in the way you want ( using one field author).
It just because one serializer cannot handle two different types for one field.
Note: i might be wrong about the previous statement.
However, the following is a potential solution for you. You just need to use different field name to create new author.
class BookSerializer(serializers.ModelSerializer):
author = serializers.PrimaryKeyRelatedField(
required=False,
queryset=Author.objects.all(),
)
author_add = AuthorSerializer(write_only=True, required=False)
class Meta:
model = Book
fields = ('id', 'title', 'author', 'author_add')
read_only_fields = ('id')
def create(self, validated_data):
author_add_data = validated_data.pop('author_add', None)
if author_add is not None:
validated_data['author'] = Author.objects.create(**author_add_data)
return super().create(validated_data)
Note: you need to handle a case where you send both author and author_add. Probably add a check into validation step and raise ValidationError if both are provided.
Offtopic hint: you dont need to explicityl state read_only_fields = ('id',) - primary keys are read-only.
For anyone else trying to do this, here is what I ended up getting working.
For my book serializer I did the following:
class BookSerializer(serializers.ModelSerializer):
# make author a foreign key/id, read-only field so that it isn't
# processed by the validator, and on read returns just the id.
class Meta:
model = Book
fields = ('id', 'title', 'author')
read_only_fields = ('id', 'author',)
# override run_validation to validate our author
def run_validation(self, data):
# run base validation. Since `author` is read_only, it will
# be ignored.
value = super(Book, self).run_validation(data)
# inject our validated author into the validated data
value['author'] = self.validate_author(data['author'])
return value
# Custom author validation
def validate_author(self, author):
errors = OrderedDict()
if isinstance(author, int): # if just a key, retrieve the author
try:
author_instance = Author.objects.get(pk=author)
except Author.DoesNotExist:
errors['author'] = "Author with pk {} does not exist.".format(author)
raise ValidationError(errors)
else: # if passed an author object...
author_serializer = AuthorSerializer(data=author, many=False)
author_serializer.is_valid(raise_exception=True)
author_instance = author_serializer.save()
return author_instance
I need to do a bit more error checking (e.g.- no author passed), but it works quite well- the consumer of the API can submit either an author id, or a serialized author object to create a new author. And the API itself returns just an id as was needed.
I could not show reverse relation in Django Rest Framework. Here in my case, a rent can have multiple images and i want to display all the images of that rent(living room image, kitchen image, bathroom image etc) in /api/v1/rent, i mean in rent resource.
So, how do I fetch these Galleries to display in the Rental resource?
class Rental(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=300, blank=False, null=False)
phone_number = models.PositiveIntegerField(null=False, blank=False)
renter = models.CharField(choices=RENTER_CHOICES, max_length=1, default=RENTER_CHOICES[0])
def __str__(self):
return self.name
def user_directory_path(instance, filename):
return 'rent_{0}/{1}'.format(instance.rent.id, filename)
class Gallery(models.Model):
rent = models.ForeignKey(Rental, related_name="rent")
image = models.FileField(upload_to=user_directory_path)
tag = models.CharField(max_length=1, choices=TAGS, null=True, blank=True)
class GallerySerializer(serializers.ModelSerializer):
rent = serializers.ReadOnlyField()
class Meta:
model = Gallery
fields = ('rent', 'image', 'tag',)
class RentalSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
# gallery = serializers.SerializerMethodField('get_children')
#
# def get_children(self, obj):
# print ('obj', obj.gallery_set)
# serializer = GallerySerializer(obj.gallery_set.all(), many=True)
# return serializer.data
gallery = GallerySerializer(source='gallery_set',many=True, read_only=True)
class Meta:
model = Rental
fields = ('user', 'name', 'phone_number','gallery',)
Right now i dont get the list of gallery, using both way, one commented way and another source='gallery_set' way.
UPDATE
if i do rent = GallerySerializer(many=True) i get the list of galleries but does it make any sense?
[
{
"user": "admin",
"name": "Sunrise Home",
"phone_number": 9842333833,
"rent": [
{
"image": "http://localhost:8000/media/rent_1/sittingRoom.jpg",
"tag": "L"
}
]
}
]
in above api you see the galleries are shown but the name shows it as a list of rent inside rental resource. Can anyone help me to design an api better way?
When you specify the foreign key relationship for Rental in your Gallery model the related_name you specify automatically defines a field on the Rental object. The name of that field is whatever you set related_name to, in this case rent.
The important thing to understand here is that the related_name will be attached to the opposite side of the relationship from where the key is declared in your models. Because the related name is going to be a property of a Rental object, a better name for it might be galleries in your example.
Assuming you change the related_name in your models from rental to galleries, you can define your Rental serializer to output a list of associated galleries:
class RentalSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
galleries = GallerySerializer(many=True, read_only=True)
class Meta:
model = Rental
fields = ('user', 'name', 'phone_number','galleries')
By declaring a GallerySerializer with the same name as the related_name defined in your foreign key relationship, it will automatically find only galleries which are associated with the Rental being serialized.
In another example:
Let's say you have several Eggs in a Basket. We would want to create a many-to-one relationship between Eggs and a Basket and would accomplish this by creating a foreign-key relationship between the two. That would be in the form of storing the ID of the associated Basket for each Egg.
In Django, we would declare that foreign key on the Egg model. This allows us to access the basket that an egg is in via egg.basket. We also want to determine what eggs are in a particular basket. We do that by defining a related_name on the foreign-key field. This tells Django what the field linking the basket to all its contained eggs should be called. Since this field refers to all eggs in a basket, we would call it eggs.
In code:
class Basket(models.Model):
color = models.CharField()
class Egg(models.Model):
color = models.CharField()
basket = models.ForeignKey(Basket, related_name="eggs")
I've a basic django model and i was wondering is there a way to make the search fields search in the value i am mapping to instead of searching in the value saved in the database, is there a possible way that i can search by the value "Premium" ?
Model.py
class User(models.Model):
account = models.ForeignKey('Account')
name =models.CharField(max_length=50)
ACCOUNT_CHOICES = (
(1, 'Premium'),
(0, 'Normal'),)
type = models.CharField(choices=ACCOUNT_CHOICES)
Admin.py
class UserAdmin(admin.ModelAdmin):
search_fields = ['name','type']
pass
admin.site.register(User,UserAdmin)
Summary from comments discussion;
You'll need to set up a custom queryset for the search filter which attempts to reverse lookup the choice display field to its value. Another slight issue is that multiple values can have the same choice display name, so you'll need to take that into consideration.
Here is an example of how to achieve this:
https://github.com/sivaa/django-custom-search-filter/blob/master/app/admin.py
Model:
class order(models.Model):
STATUS = (
("Completed", "Completed"),
("Ordered", "Ordered"),
("Accepted", "Accepted"),
("Order Cancel", "Order Cancel"),
("Customer Cancel", "Customer Cancel"),
("Delivered", "Delivered"),
("Added to Cart", "Added to Cart"),
("Out of Delivery", "Out of Delivery"),
("Refund Initiated","Refund Initiated"),
("Return And exchange","Return And exchange"),
)
status = models.CharField(default="Ordered", max_length=50, null=True, choices=STATUS, blank=True)
View:
status1=request.POST.get("status")
response1=order.objects.filter(status__contains=status1)