Ordering case insensitive in django-tables2 - python

First post in this awesome community that I have been reading a long time ago :)
I've encountered with a problem when using this fantastic library "django-tables2". When I'm ordering a column by a CharField, it does a case sensitive ordering resulting unexpected behaviour like this:
Iago
Pablo
iago
I want to be ordered in a more natural way:
Iago
iago
Pablo
This is my simplified code for the table:
class Inquiry(models.Model):
...
contact_last_name = models.CharField(max_length=50)
...
class Hometable(tables.Table):
contact_last_name = tables.Column(verbose_name="Contact", order_by=('contact_last_name'))
class Meta:
model = Inquiry
fields= ('contact_last_name',)
I know in Django 1.8 there is a built-in function Lower to make a insensitive order_by, but it doesn't work with django tables:
contact_last_name = tables.Column(verbose_name="Contact", order_by=(Lower('contact_last_name')))
It results in an exception:
TypeError at /
'Lower' object is not iterable
Has anyone done anything similar with django-tables2?
Thank you!
UPDATE: The solution is to make a annotation in the view with the lowercase fields, an then will be available to order by in the table.
class Inquiry(models.Model):
...
contact_last_name = models.CharField(max_length=50)
...
class Hometable(tables.Table):
contact_last_name = tables.Column(verbose_name="Contact", order_by=('contact_last_name_lower'))
class Meta:
model = Inquiry
fields= ('contact_last_name',)
And make the proper annotation in the queryset when configuring the table as Alasdair purposed:
inquiries = inquiries.annotate(contact_last_name_lower=Lower('last_name'))
my_table = Hometable(inquiries)

Based on https://django-tables2.readthedocs.io/en/latest/pages/ordering.html#table-order-foo-methods you need to add a table.order_FOO() method and end up with something like this:
class Hometable(tables.Table):
contact_last_name = tables.Column(verbose_name="Contact", order_by=('contact_last_name'))
def order_contact_last_name (self, QuerySet, is_descending):
QuerySet = QuerySet.annotate(
field_lower=Func(F('contact_last_name '), function='LOWER')
).order_by(('-' if is_descending else '') + 'field_lower')
return (QuerySet, True)
class Meta:
model = Inquiry
fields= ('contact_last_name',)
This should work when you click column headers to order as well as the initial ordering.

I haven't tried this, and I'm not really familiar with django-tables2 so I don't know whether it will work.
You could try using a new field name e.g. contact_last_name_lower when setting order_by for the column.
class Hometable(tables.Table):
contact_last_name = tables.Column(verbose_name="Contact", order_by=('contact_last_name_lower',))
class Meta:
model = Inquiry
fields= ('contact_last_name',)
Then, when you instantiate the table, annotate the queryset with the lower case field.
queryset = Inquiry.objects.annotate(contact_last_name_lower=Lower('contact_last_name'))
table = Hometable(queryset)

Related

Django - model with several tag fields

I need a model with several "tag" fields. For example:
class Food(models.Model):
type = SomeTagField()
manufacturer = SomeTagField()
It could be done using many-to-many relationships, but I'm not sure if this is a correct approach. It looks a little bit overcomplicated.
I've tried django-tagit and its TaggableManager but without luck:
ValueError: You can't have two TaggableManagers with the same through model.
What would be a correct way to handle that?
"Over-complicated" is subjective, but I would use ManyToManyField
class FoodType(models.Model):
country = SomeField()
nutrients = SomeField()
...
class Manufacturer(models.Model):
name = SomeField()
cost = SomeField()
...
class Food(models.Model):
type = models.ManyToManyField(FoodType, ...)
manufacturer = models.ManyToManyField(Manufacturer, ...)

Add property in Django many to many relation

I have the following Django models:
class Lesson(models.Model):
title = models.TextField()
class Course(models.Model):
lessons = models.ManyToManyField(Lesson)
class User(AbstractUser):
favorites = models.ManyToManyField(Lesson)
I have a route /courses/course_id that returns a course details including an array of lessons (using Django Rest Framework)
How can i return in the lessons object an additional attribute favorite based on my users favorites.
I attempted the following:
course = self.get_object(course_id)
favorites = request.user.favorites
for lesson in course.lessons.all():
if lesson in favorites.all():
lesson.favorite = True
serializer = CourseDetailSerializer(course, context=serializer_context)
return Response(serializer.data)
But when returning it doesn't work:
(django.core.exceptions.ImproperlyConfigured: Field name favorite is
not valid for model Lesson.
My serializers:
class CourseDetailSerializer(serializers.HyperlinkedModelSerializer):
lessons = LessonListSerializer(many=True, read_only=True)
class Meta:
model = Course
fields = ('id', 'lessons', 'name', 'title')
class LessonSerializer(serializers.ModelSerializer):
class Meta:
model = Lesson
fields = ('id', 'title', 'duration', 'favorite')
You cannot add properties to objects if they are not defined, like here:
lesson.favorite = True
When you create m2m relation:
favorites = models.ManyToManyField(Lesson)
... django creates virtual model that simply stores pairs of primary keys from both models. This relation could look like this in database:
id | user_id | lesson_id
------+---------------+----------
151 | 11 | 3225
741 | 21 | 4137
What I think you want to achieve is to add extra information about this relation.
Therefore you need to create intermediary model with that extra field, i.e:
class User(AbstractUser):
favorites = models.ManyToManyField(Lesson, through='UserLessons')
class UserLessons(models.Model):
user = models.ForeignKey(User)
lesson = models.ForeignKey(Lesson)
favorite = models.BooleanField(default=False)
Your lessonmodel doesn't include a favourite boolean, so it isn't able to set it when you call lesson.favorite = True
If you want to get rid of the error, try:
class Lesson(models.Model):
title = models.TextField()
favorite = models.BooleanField(initial=False)
Although it appears that the lessons aren't user-specific. So this solution might not be what you are looking for, because it will set a Lesson's "favourite" field to be true for all users if only one user sets it as a favorite.

Django Access to Foreign Key data to set a field default value

I have two models with their respective forms. One has a Foreign Key link to the other and from, here I would like to set some fields default data.
class Lexicon(models.Model):
[...]
case_sensitive = models.BooleanField(default=True)
invariant = models.NullBooleanField(default=False)
diacritics = models.BooleanField(default=True)
[...]
class Meta:
verbose_name = "lexicon"
ordering = ["filename"]
def __str__(self):
return self.filename
class Lexeme(models.Model):
lexicon = models.ForeignKey(Lexicon, on_delete=models.CASCADE)
case_sensitive = models.BooleanField(default=True)
diacritics = models.BooleanField(default=True)
[...]
class Meta:
verbose_name = "lexeme"
I would like the Lexeme model fields "case_sensitive" and "diacritics" to default from Lexicon. I suppose the forms may be a better place to do this.
Any idea ?
As I understand, you only need to populate data from Lexicon to Lexeme model fields. You can override get_form_kwargs in your FormView as follows
def get_form_kwargs(self):
lex_obj = Lexeme.objects.get(pk=self.kwargs['pk'])
kwargs = super().get_form_kwargs()
kwargs['initial']['case_sensitive'] = lex_obj.lexicon.case_sensitive
kwargs['initial']['diacritics'] = lex_obj.lexicon.diacritics
return kwargs
Is that what you want? I have not tested but, I have used similar thing on my project. Let me know if works or not.
I finally found the way to go. It was just basic initial setting of field, no need to touch to forms.py, models.py nor the html template.
I passed data to my form like this:
lexeme_form = LexemeForm(initial={'case_sensitive': lexicon.case_sensitive, 'diacritics': lexicon.diacritics})
use Ajax at template to change the initial value of "case_sensitive" and "diacritics" when Lexicon changed, and abstract model can be used to reduce repeat lines :
class BaseLex(models.Model):
case_sensitive = models.BooleanField(default=True)
diacritics = models.BooleanField(default=True)
class Meta:
abstract = True
class Lexicon(BaseLex):
# without `case_sensitive` and `diacritics' fields
...
class Lexeme(BaseLex):
# without `case_sensitive` and `diacritics' fields
lexicon = models.ForeignKey(Lexicon, on_delete=models.CASCADE)
...

DRF changing field name values of django models with foreign keys

I followed suggestion from this question
But i need to name one field of query_set to date filed of another object
My models are
class Choice(models.Model):
question = models.ForeignKey(Question, related_name='choice', on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
class ChoiceWithTime(models.Model):
choiceTime = models.ForeignKey(Choice,related_name='choiceTime', on_delete=models.CASCADE)
choice_date=models.DateField()
My view
class QuestionChoicesViewSet(viewsets.ModelViewSet):
queryset = Choice.objects.all()
serializer_class = ChoiceDateSerializer
def get_queryset(self):
return Choice.objects.values('choiceTime__choice_date','choice_text').annotate(
total_votes=Count('choiceTime__choice_date'),
)
I need to count number of submission in particular dates
I don't know how to name choiceTime__choice_date that serializer recognizes field in query set
class ChoiceDateSerializer(serializers.ModelSerializer):
choiceTime__choice_date = serializers.DateTimeField()
total_votes = serializers.IntegerField()
class Meta:
model = Choice
fields = ('id', 'choice_text','total_votes','choiceTime__choice_date')
i receive
{
"choice_text": "ant tower",
"total_votes": 3,
"choiceTime__choice_date": "2017-04-20"
}
But i want to recieve
{
"choice_text": "ant tower",
"total_votes": 3,
"choice_date": "2017-04-20"
}
Tried different options with no success. Definitely i am missing the point.
For my purposes it is working, but i want to have well written API.
2 option change time submission model?
class ChoiceWithTime(models.Model):
choiceTime = models.ForeignKey(Choice,related_name='choiceTime', on_delete=models.CASCADE)
choice_date=models.DateField()
coutner = models.IntegerField(default=0)
Is 2 option considers to be better approach to my particular problem? Thanks!
You are receiving a json object, which you add its key value.
for vote_detail in data:
if vote_detail.choiceTime__choice_date:
vote_detail.choice_date=vote_detail.choiceTime__choice_date
then serialize and save, a quick solution.
You could also add to your model the name that you want to call it. That's closer to backend and maybe worth delving into.
from django.db.models import Count,F
If anybody finds this problem and this is easiest answer i came up to.
As it was suggested before passing to serializer change value using model package functions
class QuestionChoicesViewSet(viewsets.ModelViewSet):
queryset = Choice.objects.all()
serializer_class = ChoiceDateSerializer
def get_queryset(self):
return Choice.objects.all().annotate(choice_date=F('choiceTime__choice_date')).values('choice_date','choice_text').annotate(
total_votes=Count('choiceTime__choice_date'),
)

Django Rest Framework: dynamic database on POST - RelatedField or PrimaryKeyRelatedField

I'm developing RESTFul services with DRF and I have multiple databases depending on the country (see my last question here)
I'm having a problem now with relationships, I have two models: Category and SubCategory:
class SubCategory(models.Model):
objects = CountryQuerySet.as_manager()
id = models.AutoField(primary_key=True,db_column='sub_category_id')
name = models.TextField()
female_items_in_category = models.BooleanField()
male_items_in_category = models.BooleanField()
kids_items_in_category = models.BooleanField()
category = models.ForeignKey('Category')
class Meta:
managed = True
db_table = Constants().SUBCATEGORY
And the serializer is:
class SubCategorySerializer(serializers.ModelSerializer):
category = PrimaryKeyRelatedField(queryset=Category.objects.using('es').all())
class Meta:
model = SubCategory
fields = ('id', 'name','female_items_in_category','male_items_in_category','kids_items_in_category','category')
If I don't set the queryset with the proper country it fails, because it doesn't know where to get the category.
Here the problem
I already set the country in the serializer context (in the ModelViewSet):
def get_serializer_context(self):
return {Constants().COUNTRY: self.kwargs.get(Constants().COUNTRY)}
But I can not find the proper way to get the self.context.get(Constants().COUNTRY) in the serializer.
Do you any have an idea to solve this? Thanks!
Well, I found a solution to my problem: I overwrite the method get_fields in the serializer:
def get_fields(self, *args, **kwargs):
fields = super(SubCategorySerializer, self).get_fields()
country = self.context.get(Constants().COUNTRY)
qs = Category.objects.using(country).all()
fields['category'].queryset = qs
return fields
And that works!

Categories

Resources