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'])
Related
class Parent(models.Model):
# some fields
class Child(models.Model):
parent = models.ForeginKey(Parent)
species = models.ForeignKey(Species)
# other fields
I have a function like this:
1. def some_function(unique_id):
2. parent_object = Parent.objects.get(unique_id=unique_id)
3. new_child = Child.objects.create(name='Joe', parent=parent_object)
4. call_some_func(new_child.parent.name, new_child.species.name)
In the line 4, a db query is generated for Species. Is there any way, I can use select_related to prefetch the Species, so as to prevent extra query.
Can it be done while I use .create(). This is just an example , I am using many other fields too and they are querying the DB every time.
The only way I can think is after line 3 using this code:
child_obj = Child.objects.select_related('species').get(id=new_child.id)
The only parameter that create accepts is force_insert which is not related to what you're asking, so it seems it's not possible. Also, noticing that create performs an INSERT ... RETURNING ... statement, I don't think it would be possible anyway because you cannot return columns from other tables.
Possibly the best approach is what you already suggested: do a get() afterwards with the related fields you need.
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
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.
I have a really strange problem with Django 1.4.4.
I have this model :
class LogQuarter(models.Model):
timestamp = models.DateTimeField()
domain = models.CharField(max_length=253)
attempts = models.IntegerField()
success = models.IntegerField()
queue = models.IntegerField()
...
I need to gather the first 20 domains with the higher sent property. The sent property is attempts - queue.
This is my request:
obj = LogQuarter.objects\
.aggregate(Sum(F('attempts')-F('queue')))\
.values('domain')\
.filter(**kwargs)\
.order_by('-sent')[:20]
I tried with extra too and it isn't working.
It's really basic SQL, I am surprised that Django can't do this.
Did someone has a solution ?
You can actually do this via subclassing some of the aggregation functionality. This requires digging in to the code to really understand, but here's what I coded up to do something similar for MAX and MIN. (Note: this code is based of Django 1.4 / MySQL).
Start by subclassing the underlying aggregation class and overriding the as_sql method. This method writes the actual SQL to the database query. We have to make sure to quote the field that gets passed in correctly and associate it with the proper table name.
from django.db.models.sql import aggregates
class SqlCalculatedSum(aggregates.Aggregate):
sql_function = 'SUM'
sql_template = '%(function)s(%(field)s - %(other_field)s)'
def as_sql(self, qn, connection):
# self.col is currently a tuple, where the first item is the table name and
# the second item is the primary column name. Assuming our calculation is
# on two fields in the same table, we can use that to our advantage. qn is
# underlying DB quoting object and quotes things appropriately. The column
# entry in the self.extra var is the actual database column name for the
# secondary column.
self.extra['other_field'] = '.'.join(
[qn(c) for c in (self.col[0], self.extra['column'])])
return super(SqlCalculatedSum, self).as_sql(qn, connection)
Next, subclass the general model aggregation class and override the add_to_query method. This method is what determines how the aggregate gets added to the underlying query object. We want to be able to pass in the field name (e.g. queue) but get the corresponding DB column name (in case it is something different).
from django.db import models
class CalculatedSum(models.Aggregate):
name = SqlCalculatedSum
def add_to_query(self, query, alias, col, source, is_summary):
# Utilize the fact that self.extra is set to all of the extra kwargs passed
# in on initialization. We want to get the corresponding database column
# name for whatever field we pass in to the "variable" kwarg.
self.extra['column'] = query.model._meta.get_field(
self.extra['variable']).db_column
query.aggregates[alias] = self.name(
col, source=source, is_summary=is_summary, **self.extra)
You can then use your new class in an annotation like this:
queryset.annotate(calc_attempts=CalculatedSum('attempts', variable='queue'))
Assuming your attempts and queue fields have those same db column names, this should generate SQL similar to the following:
SELECT SUM(`LogQuarter`.`attempts` - `LogQuarter`.`queue`) AS calc_attempts
And there you go.
I am not sure if you can do this Sum(F('attempts')-F('queue')). It should throw an error in the first place. I guess, easier approach would be to use extra.
result = LogQuarter.objects.extra(select={'sent':'(attempts-queue)'}, order_by=['-sent'])[:20]
this is a model of the view table.
class QryDescChar(models.Model):
iid_id = models.IntegerField()
cid_id = models.IntegerField()
cs = models.CharField(max_length=10)
cid = models.IntegerField()
charname = models.CharField(max_length=50)
class Meta:
db_table = u'qry_desc_char'
this is the SQL i use to create the table
CREATE VIEW qry_desc_char as
SELECT
tbl_desc.iid_id,
tbl_desc.cid_id,
tbl_desc.cs,
tbl_char.cid,
tbl_char.charname
FROM tbl_desC,tbl_char
WHERE tbl_desc.cid_id = tbl_char.cid;
i dont know if i need a function in models or views or both. i want to get a list of objects from that database to display it. This might be easy but im new at Django and python so i having some problems
Django 1.1 brought in a new feature that you might find useful. You should be able to do something like:
class QryDescChar(models.Model):
iid_id = models.IntegerField()
cid_id = models.IntegerField()
cs = models.CharField(max_length=10)
cid = models.IntegerField()
charname = models.CharField(max_length=50)
class Meta:
db_table = u'qry_desc_char'
managed = False
The documentation for the managed Meta class option is here. A relevant quote:
If False, no database table creation
or deletion operations will be
performed for this model. This is
useful if the model represents an
existing table or a database view that
has been created by some other means.
This is the only difference when
managed is False. All other aspects of
model handling are exactly the same as
normal.
Once that is done, you should be able to use your model normally. To get a list of objects you'd do something like:
qry_desc_char_list = QryDescChar.objects.all()
To actually get the list into your template you might want to look at generic views, specifically the object_list view.
If your RDBMS lets you create writable views and the view you create has the exact structure than the table Django would create I guess that should work directly.
(This is an old question, but is an area that still trips people up and is still highly relevant to anyone using Django with a pre-existing, normalized schema.)
In your SELECT statement you will need to add a numeric "id" because Django expects one, even on an unmanaged model. You can use the row_number() window function to accomplish this if there isn't a guaranteed unique integer value on the row somewhere (and with views this is often the case).
In this case I'm using an ORDER BY clause with the window function, but you can do anything that's valid, and while you're at it you may as well use a clause that's useful to you in some way. Just make sure you do not try to use Django ORM dot references to relations because they look for the "id" column by default, and yours are fake.
Additionally I would consider renaming my output columns to something more meaningful if you're going to use it within an object. With those changes in place the query would look more like (of course, substitute your own terms for the "AS" clauses):
CREATE VIEW qry_desc_char as
SELECT
row_number() OVER (ORDER BY tbl_char.cid) AS id,
tbl_desc.iid_id AS iid_id,
tbl_desc.cid_id AS cid_id,
tbl_desc.cs AS a_better_name,
tbl_char.cid AS something_descriptive,
tbl_char.charname AS name
FROM tbl_desc,tbl_char
WHERE tbl_desc.cid_id = tbl_char.cid;
Once that is done, in Django your model could look like this:
class QryDescChar(models.Model):
iid_id = models.ForeignKey('WhateverIidIs', related_name='+',
db_column='iid_id', on_delete=models.DO_NOTHING)
cid_id = models.ForeignKey('WhateverCidIs', related_name='+',
db_column='cid_id', on_delete=models.DO_NOTHING)
a_better_name = models.CharField(max_length=10)
something_descriptive = models.IntegerField()
name = models.CharField(max_length=50)
class Meta:
managed = False
db_table = 'qry_desc_char'
You don't need the "_id" part on the end of the id column names, because you can declare the column name on the Django model with something more descriptive using the "db_column" argument as I did above (but here I only it to prevent Django from adding another "_id" to the end of cid_id and iid_id -- which added zero semantic value to your code). Also, note the "on_delete" argument. Django does its own thing when it comes to cascading deletes, and on an interesting data model you don't want this -- and when it comes to views you'll just get an error and an aborted transaction. Prior to Django 1.5 you have to patch it to make DO_NOTHING actually mean "do nothing" -- otherwise it will still try to (needlessly) query and collect all related objects before going through its delete cycle, and the query will fail, halting the entire operation.
Incidentally, I wrote an in-depth explanation of how to do this just the other day.
You are trying to fetch records from a view. This is not correct as a view does not map to a model, a table maps to a model.
You should use Django ORM to fetch QryDescChar objects. Please note that Django ORM will fetch them directly from the table. You can consult Django docs for extra() and select_related() methods which will allow you to fetch related data (data you want to get from the other table) in different ways.