I have model with field named "number". It's not the same as id, it's position while displayed on website, users can sort teams around.
I need something for default property of number. I think I should just count the number of positions in the database and add one, so if there are 15 positions inside db, the new team would be last on list and have "number" 16.
But I don't know how to do it, inside models.py file.
Inside views.py I would just use
Iteam.objects.count()
But what can I use inside model declaration? How can the model check itself?
Edit:
I tried do it as Joey Wilhelm suggested:
from django.db import models
class ItemManager(models.Manager):
def next_number(self):
return self.count() + 1
class Iteam(models.Model):
name = models.CharField()
objects = ItemManager()
number = models.IntegerField(default=objects.next_number())
Unfortunetly i get error:
AttributeError: 'NoneType' object has no attribute '_meta'
This is actually something you would want to do on a manager, rather than the model itself. The model should represent, and perform actions for, an individual instance. Whereas the manager represents, and performs actions for a collection of instances. So in this case you might want something like:
from django.db import models
class ItemManager(models.Manager):
def next_number(self):
return self.count() + 1
class Item(models.Model):
number = models.IntegerField()
objects = ItemManager()
That being said, I could see this leading to a lot of data integrity issues. Imagine the scenario:
4 items are created, with numbers 1, 2, 3, 4
Item 2 gets deleted
1 new item is created; this item now has a number of 4, giving you a duplicate
Edit:
The above approach will work only for pre-generating 'number', such as:
Iteam.objects.create(number=Iteam.objects.get_number(), name='foo')
In order to have it work as an actual default value for the field, you would need to use a less savory approach, such as:
from django.db import models
class Iteam(models.Model):
name = models.CharField()
number = models.IntegerField(default=lambda: Iteam.get_next_number())
#classmethod
def get_next_number(cls):
return cls.objects.count() + 1
I would still warn against this approach, however, as it still could lead to the data integrity issues mentioned above.
Related
I have a recipe, and it has a list of steps, related like so:
class Recipe(Model):
name = CharField()
class Step(Model):
recipe = ForeignKey('Recipe', related_name='steps')
Now when editing the recipe, I want to be able to insert a new step between two others. But when I create a new step, it always get added to the end of recipe.steps. Using recipe.steps.set([step_1, step_2, step_3]) doesn't work, as apparently set() only works on nullable fields. I'd like to avoid having to change the model just to support this, but I can't seem to find a way to do this otherwise.
The items can be retrieved in a random order with the current modeling: as long as you do not specify an ordering in the Meta of the Step module, or you do not use (an explicit) .order_by(…) [Django-doc], the database can decide to give it any particular order.
What you can do is add a FloatField field sequence_number field to your Step model:
from django.db import models
class Step(models.Model):
recipe = models.ForeignKey('Recipe', related_name='steps')
sequence_number = models.FloatField(default=1)
class Meta:
ordering = ['sequence_number']
The advantage for this is that if you want to create a new step between 1 and 2, you can make use of 1.5, and if you later need to add a step between 1.5 and 2, you can use 1.75.
By working with ordering (or with .order_by('sequence_number'), we thus can determine the step of that item.
We can also rewrite the sequence to integral numbers with:
def reorder_steps(steps):
steps = list(steps)
for i, step in enumerate(steps, 1):
step.sequence_number = i
Step.objects.bulk_update(steps, ['sequence_number'])
I'm building a web app using the Django framework and the django-import-export package.
I would like to import data from files and want to prevent importing it twice to the DB.
For this, I used the import_id_fields when declaring the resource class, but it seems that it doesn't work as expected.
1- The first time I import the file everything is working fine and rows created in the DB.
2- The second time I import the same file, also new rows created in the DB (here is the problem, this is not supposed to happen)
3- The third time I import the same file, here I get errors and no rows added to the DB.
So I would like to know if this is normal behavior or not, and if is normal I would like to know how can I stop the import in point 2 and show the errors.
You can find below portions from the code and the error messages.
# resources.py
class OfferingResource(ModelResource):
ACCESSIONNUMBER = Field(attribute='company',
widget=ForeignKeyWidget(Company, 'accession_number'))
quarter = Field(attribute='quarter')
# other fields ...
class Meta:
model = Offering
import_id_fields = ('ACCESSIONNUMBER', 'quarter')
def before_import_row(self, row, row_number=None, **kwargs):
Company.objects.get_or_create(accession_number=row.get('ACCESSIONNUMBER'))
# models.py
class Company(models.Model):
accession_number = models.CharField(max_length=25, primary_key=True)
def __str__(self):
return self.accession_number
class Offering(models.Model):
company = models.ForeignKey(Company, on_delete=models.CASCADE, to_field='accession_number')
quarter = models.CharField(max_length=7)
# other fields ...
def __str__(self):
return f'{self.company}
<- error messages ->
1 get() returned more than one Offering -- it returned 2!
2 get() returned more than one Offering -- it returned 2!
...
22 get() returned more than one Offering -- it returned 2!
You are on the right track but your implementation is not quite right.
You are right to use import_id_fields. The fields you declare here must uniquely identify a single instance of a row object.
The object instance is identified using:
return self.get_queryset().get(**params)
where each param has a key of your attribute field name, and the value is the cleaned value taken from the import file.
Therefore, this line won't work:
import_id_fields = ('ACCESSIONNUMBER', 'quarter')
because you don't have an attribute on Offering called ACCESSIONNUMBER. To fix this, declare your field using this syntax:
industry_group_type = Field(attribute='company__accession_number', column_name='ACCESSIONNUMBER')
(you need to follow the model relation to refer to accession_number. I assume your csv import column name is ACCESSIONNUMBER).
If this combination of fields does not uniquely identify a single instance, then you will see the error:
get() returned more than one Offering -- it returned 2!
I have a model with a field that is a int type, that field have a default value
I'm trying to set a value in that field with post_generation but nothing is happening, the field stay with the default value, when I try to use .set I get the following error:
AttributeError: 'int' object has no attribute 'set'
this is the field that I'm trying to populate
#factory.post_generation
def priority(obj, create, extracted, **kwargs):
for series in range(obj.patrimony.count()): # this is a sequence of numbers
series += 1
obj.priority.set(series)
and this is the model, is just a simple model
class Series(models.Model):
priority = models.IntegerField(_("Priority"), default=0, null=True)
Can someone open my eyes please?
You are meeting two issues:
Setting the field value
Series.priority is always an int, and integers have no .set() method (they are immutable objects).
You should set it by using obj.priority = series
Setting the value at the right time
factory_boy creates objects in 3 steps:
1. Evaluate all pre-declarations (LazyAttribute, Sequence, etc.);
2. Create the object in the database (calling Series.objects.create(...))
3. Evaluate post-generation declarations
If obj.patrimony is known before creating the series, you could simply have:
class SeriesFactory(factory.django.DjangoModelFactory):
class Meta:
model = Series
priority = factory.LazyAttribute(lambda o: o.patrimony.count())
(I've also adjusted the declaration, since your for series in ... loop is strictly equivalent to obj.priority = obj.patrimony.count())
We are currently running with the following configuration to avoid other issues.
So for the question: let's assume that this is a must and we can not change the Models part.
At the beginning we had the following models:
class A(Model):
b = ForeignKey(B)
... set of fields ...
class B(Model):
...
Then we added something like this:
class AVer2(Model):
b = ForeignKey(B)
... ANOTHER set of fields ...
Assuming an object of type B can only be referenced by either A or AVer2 but never both:
Is there a way to run a query on B that will return, at runtime, the correct object type that references it, in the query result (and the query has both types in it)?
You can assume that an object of type B holds the information regarding who's referencing it.
I am trying to avoid costly whole-system code changes for this.
EDIT:
Apparently, my question was not clear. So I will try to explain it better. The answers I got were great but apparently I missed a key point in my question so here it is. Assuming I have the model B from above, and I get some objects:
b_filter = B.objects.filter(some_of_them_have_this_true=True)
Now, I want to get a field that is in both A and AVer2 with one filter into one values list. So for example, I want to get a field named "MyVal" (both A and AVer2 have it) I don't care what is the actual type. So I want to write something like:
b_filter.values(['a__myval', 'aver2__myval'])
and get something like the following in return: [{'myval': }]
Instead, I currently get [{'a__myval': , 'aver2__myval': None}]
I hope it is clearer.
Thanks!
Short answer: You can not make your exact need.
Long answer: The first thing that came to my mind when I read your question is Content Types, Generic Foreign Keys and Generic Relations
Whether you will use "normal" foreign keys or "generic foreign keys" (combined with Generic Relation), Your B instances will have both A field and AVer2 field and this natural thing make life easier and make your goal (B instance has a single Field that may be A or Avr2) unreachable. And here you should also override the B model save method to force it to have only the A field and the Avr2 to be None or A to be None and Avr2 to be used. And if you do so, don't forget to add null=True, blank=True to A and Avr2 foreign key fields.
On the other hand, the opposite of your schema makes your goal reachable:
B model references A and Avr2 that means that B model has ONE generic foreign key to both A and Avr2 like this: (this code is with Django 1.8, for Django 1.9 or higher the import of GenericRelation, GenericForeignKey has changed)
from django.db import models
from django.contrib.contenttypes.generic import GenericRelation, GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class B(models.Model):
# Some of your fields here...
content_type = models.ForeignKey(ContentType, null=True, blank=True)
object_id = models.PositiveIntegerField(null=True, blank=True)
# Generic relational field will be associed to diffrent models like A or Avr2
content_object = GenericForeignKey('content_type', 'object_id')
class A(models.Model):
# Some of your fields here...
the_common_field = models.BooleanField()
bbb = GenericRelation(B, related_query_name="a") # since it is a foreign key, this may be one or many objects refernced (One-To-Many)
class Avr2(models.Model):
# Some of your fields here...
the_common_field = models.BooleanField()
bbb = GenericRelation(B, related_query_name="avr2") # since it is a foreign key, this may be one or many objects refernced (One-To-Many)
Now both A and Avr2 have "bbb" field which is a B instance.
a = A(some fields initializations)
a.save()
b = B(some fields initializations)
b.save()
a.bbb = [b]
a.save()
Now you can do a.bbb and you get the B instances
And get the A or Avr2 out of b like this:
b.content_object # which will return an `A object` or an `Avr2 object`
Now let's return to your goals:
Is there a way to run a query on B that will return, at runtime, the correct object type that references it, in the query result (and the query has both types in it)?
Yes: like this:
B.objects.get(id=1).content_type # will return A or Avr2
You wanna perform something like this: b_filter = B.objects.filter(some_of_them_have_this_true=True) :
from django.db.models import Q
filter = Q(a__common_field=True) | Q(avr2__common_field=True)
B.objects.filter(filter)
Getting [{'a__myval': , 'aver2__myval': None}] is 100% normal since values is asked to provide two fields values. One way to overcome this, is by getting two clean queries and then chain them together like so:
from itertools import chain
c1 = B.objects.filter(content_type__model='a').values('a__common_field')
c2 = B.objects.filter(content_type__model='avr2').values('avr2__common_field')
result_list = list(chain(c1, c2))
Please notice that when we added related_query_name to the generic relation, a and avr2 has become accessible from B instances, which is not the default case.
And voilà ! I hope this helps !
I'm not sure what do you want to get in query set.
I assumed that you want set of "correct object types" that "has both types in it", so in fact you want set of related class types (like [<class 'main.models.A'>, <class 'main.models.A2'>]). If that is not the case, I can change answer after more specific details in comments.
This is solution for that "class list", you can use it to get what you precisely want.
# Our custom QuerySet that with function that returns list of classes related to given B objects
class CustomQuerySet(models.QuerySet):
def get_types(self, *args, **kwargs):
all_queryset = self.all()
return [b.get_a() for b in all_queryset]
# Our custom manager - we make sure we get CustomQuerySet, not QuerySet
class TypesManager(models.Manager):
def get_queryset(self, *args, **kwargs):
return CustomQuerySet(self.model)
class B(models.Model):
# some fields
# Managers
objects = models.Manager()
a_types_objects = TypesManager()
# Get proper A "type"
def get_a(self):
if self.a_set.all() and self.a2_set.all():
raise Exception('B object is related to A and A2 at the same time!')
elif self.a_set.all():
return A
elif self.a2_set.all():
return A2
return None
class A(models.Model):
b = models.ForeignKey(
B
)
class A2(models.Model):
b = models.ForeignKey(
B
)
And now you can use it like this:
>>> from main.models import *
>>> B.a_types_objects.all()
<CustomQuerySet [<B: B object>, <B: B object>]>
>>> B.a_types_objects.all().get_types()
[<class 'main.models.A'>, <class 'main.models.A2'>]
>>> B.a_types_objects.filter(id=1)
<CustomQuerySet [<B: B object>]>
>>> B.a_types_objects.filter(id=1).get_types()
[<class 'main.models.A'>]
Using a_types_objects works like normal objects, but it returns CustomQuerySet, which has extra function returning list of class.
EDIT:
If you worrying about changing a lot of B.objects.(...) into B.a_types_objects.(...) you could just set your main manager to TypesManager like that:
class B(models.Model):
# some fields
# Override manager
objects = TypesManager()
Rest of your code will remain intact, but from now on you will use CustomQuerySet instead of QuerySet - still, nothing really changes.
I'd like to update multiple integer fields at once in following model.
class Foo(models.Model):
field_a = models.PositiveIntegerField()
field_b = models.PositiveIntegerField()
field_c = models.PositiveIntegerField()
Originally, it can be done like following code with two queries.
foo = Foo.objects.get(id=1)
foo.field_a += 1
foo.field_b -= 1
foo.field_c += 2
foo.save()
I'd like make it more simpler with update in one query.
However, following attempts raised error.
# 1st attempt
Foo.objects.filter(id=1).update(
field_a=F('field_a')+1,
field_b=F('field_a')-1,
field_c=F('field_a')+2)
# 2nd attempt
Foo.objects.filter(id=1).\
update(field_a=F('field_a')+1).\
update(field_b=F('field_b')-1) ).\
update(field_c=F('field_c')+2)
How can I solve this ?
Form the django docs:
Calls to update can also use F expressions to update one field based on the value of another field in the model. This is especially useful for incrementing counters based upon their current value. For example, to increment the pingback count for every entry in the blog:
>>> from django.db.models import F
>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
You have to have an instance of Foo or a queryset before you can update. You should do something like this:
Foo.objects.get(id=1)update(field_a=F('field_a')+1).\
update(field_b=F('field_b')-1) ).\
update(field_c=F('field_c')+2)
or
Foo.objects.filter(id__in=[1,3,6,7]).update(field_a=F('field_a')+1).\
update(field_b=F('field_b')-1) ).\
update(field_c=F('field_c')+2)
Reference: https://docs.djangoproject.com/en/1.8/topics/db/queries/#updating-multiple-objects-at-once
If save() is passed a list of field names in keyword argument update_fields, only the fields named in that list will be updated. This may be desirable if you want to update just one or a few fields on an object. There will be a slight performance benefit from preventing all of the model fields from being updated in the database. For example:
product.name = 'Name changed again'
product.save(update_fields=['name'])
see more docs [here]:https://docs.djangoproject.com/en/dev/ref/models/instances/#specifying-which-fields-to-save