I was programming a function in python that required a group of objects of a model to be ordered by a specific parameter (the codename of a problem), this parameter resulted to be a string. When I sorted it inside the Problems table, it worked correctly, however, when I sorted it inside ProblemUser model (using order_by) the sort was completely incorrect respecting the real results.
I managed to work it around by sorting the elements AFTER i got it from the model using sorted() function, however, i am still with the doubt on how to sort a foreign key in the order_by function.
class Problem(models.Model):
problem_name = models.CharField(max_length=256,default='Unnamed')
source = models.CharField(max_length=200)
url = models.CharField(max_length=200)
codename = models.CharField(max_length=200, primary_key=True)
difficulty = models.CharField(max_length=32,default='UNRATED')
class ProblemUser(models.Model):
problem = models.ForeignKey(Problem)
handler = models.ForeignKey(Handler)
solved = models.BooleanField(default=True)
voted = models.BooleanField(default=False)
entry = models.DateField()
t = ProblemUser.objects.filter(solved=1, handler=Handler.objects.get(id=1)).order_by('problem')
t[0].problem.codename < t[42].problem.codename
False
t[1].problem.codename < t[42].problem.codename
True
I also tried order_by('problem__codename')
Some codenames examples are (these are outputs that i remember to have seen when i ordered by the foreign key):
S_1_T
S_1000_E
S_1001_F
.
.
.
S_2_P
Thank you for your time and help! :).
try it like that please.
t = ProblemUser.objects.filter(solved=1, handler=Handler.objects.get(id=1)).order_by('problem__codename')
Related
I know the id is unique by itself, I want to create a unique field thats longer and less easy to guess. In short, this field will be a unique, 21 character alphanumeric string. I am doing this using this code below:
class Organization(models.Model):
name = models.CharField(max_length=255)
key = models.CharField(max_length=21, unique=True, blank=True, null=True)
def save(self, *args, **kwargs):
if self.key == '' or self.key == None:
key = ''
for i in range(21):
key += random.choice(string.lowercase + string.uppercase + string.digits)
if Organization.objects.filter(key=key).exists():
self.save() # Try again
else:
self.key = key
super(Organization, self).save(*args, **kwargs)
In the save method, I do the following things:
See if the object's key exists or not.
If not, I generate a 21 character key.
Then I check if an Organization with the key I generated exists or not.
If not, then the object's key is set to the generated one and saved.
Is this the right way to do this?
Is there a better way with less code perhaps? Also, notice how my key field is unique, but also can be blank. Can that be an issue? So far, this has worked pretty well. Any help is appreciated.
Right now it appears that you are trying to emulate a UUID (universal unique identifier) using a CharField. While this approach probably won't cause too many issues (it does offer, after all, (26 + 26 + 10)**21 possible combinations), it is a pretty naive approach.
Why not use Django's native UUIDField? Here is an example:
class Organization(models.Model):
name = models.CharField(max_length=255)
key = models.UUIDField(default=uuid.uuid4, editable=False)
This will use the uuid Python module. It will generate a random UUID:
In [1]: uuid.uuid4()
Out[1]: UUID('3be0e5db-eb0a-4f09-bda3-37006760ab38')
In [2]: uuid.uuid4()
Out[2]: UUID('8d916804-0a53-478e-8e4f-ebda5d7a69ab')
Obviously, these UUID's are hexadecimal and contain hyphens, but I think it's preferable to follow this convention rather than to define your own unique identifier.
You can uuid4. But personally I prefer just CharField and assign the uuid4 characters to default. The reason for this is UUIDField will give lots of inconveniences while querying DB.
If you want to add a row (by insert raw DB command) after generating the UUID Code in a pythonic way, it will give you some problems if you try to iterate through DJANGO-QUERIED objects.
by assigning uuid code to default like below,
from django.db import models
import uuid
class Person(models.Model):
name = models.CharField(max_length=255)
code = models.CharField(max_length=8, unique=True, default=uuid.uuid4)
class Model1(models.Model):
username = models.CharField(max_length=100,null=False,blank=False,unique=True)
password = models.CharField(max_length=100,null=False,blank=False)
class Model2(models.Model):
name = models.ForeignKey(Model1, null=True)
unique_str = models.CharField(max_length=50,null=False,blank=False,unique=True)
city = models.CharField(max_length=100,null=False,blank=False)
class Meta:
unique_together = (('name', 'unique_str'),)
I've already filled 3 sample username-password in Model1 through django-admin page
In my views I'm getting this list as
userlist = Model1.objects.all()
#print userlist[0].username, userlist[0].password
for user in userlist:
#here I want to get or create model2 object by uniqueness defined in meta class.
#I mean unique_str can belong to multiple user so I'm making name and str together as a unique key but I dont know how to use it here with get_or_create method.
#right now (without using unique_together) I'm doing this (but I dont know if this by default include unique_together functionality )
a,b = Model2.objects.get_or_create(unique_str='f3h6y67')
a.name = user
a.city = "acity"
a.save()
What I think you're saying is that your logical key is a combination of name and unique_together, and that you what to use that as the basis for calls to get_or_create().
First, understand the unique_together creates a database constraint. There's no way to use it, and Django doesn't do anything special with this information.
Also, at this time Django cannot use composite natural primary keys, so your models by default will have an auto-incrementing integer primary key. But you can still use name and unique_str as a key.
Looking at your code, it seems you want to do this:
a, _ = Model2.objects.get_or_create(unique_str='f3h6y67',
name=user.username)
a.city = 'acity'
a.save()
On Django 1.7 you can use update_or_create():
a, _ = Model2.objects.update_or_create(unique_str='f3h6y67',
name=user.username,
defaults={'city': 'acity'})
In either case, the key point is that the keyword arguments to _or_create are used for looking up the object, and defaults is used to provide additional data in the case of a create or update. See the documentation.
In sum, to "use" the unique_together constraint you simply use the two fields together whenever you want to uniquely specify an instance.
I have a model 'Status' with a ManyToManyField 'groups'. Each group has a ManyToManyField 'users'. I want to get all the users for a certain status. I know I can do a for loop on the groups and add all the users to a list. But the users in the groups can overlap so I have to check to see if the user is already in the group. Is there a more efficient way to do this using queries?
edit: The status has a list of groups. Each group has a list of users. I want to get the list of users from all the groups for one status.
Models
class Status(geomodels.Model):
class Meta:
ordering = ['-date']
def __unicode__(self):
username = self.user.user.username
return "{0} - {1}".format(username, self.text)
user = geomodels.ForeignKey(UserProfile, related_name='statuses')
date = geomodels.DateTimeField(auto_now=True, db_index=True)
groups = geomodels.ManyToManyField(Group, related_name='receivedStatuses', null=True, blank=True)
class Group(models.Model):
def __unicode__(self):
return self.name + " - " + self.user.user.username
name = models.CharField(max_length=64, db_index=True)
members = models.ManyToManyField(UserProfile, related_name='groupsIn')
user = models.ForeignKey(UserProfile, related_name='groups')
I ended up creating a list of the groups I was looking for and then querying all users that were in any of those groups. This should be pretty efficient as I'm only using one query.
statusGroups = []
for group in status.groups.all():
statusGroups.append(group)
users = UserProfile.objects.filter(groupsIn__in=statusGroups)
As you haven't posted your models, its a bit difficult to give you a django queryset answer, but you can solve your overlapping problem by adding your users to a set which doesn't allow duplicates. For example:
from collections import defaultdict
users_by_status = defaultdict(set)
for i in Status.objects.all():
for group in i.group_set.all():
users_by_status[i].add(group.user.pk)
Based on your posted model code, the query for a given status is:
UserProfile.objects.filter(groupsIn__receivedStatuses=some_status).distinct()
I'm not 100% sure that the distinct() call is necessary, but I seem to recall that you'd risk duplicates if a given UserProfile were in multiple groups that share the same status. The main point is that filtering on many-to-many relationships works using the usual underscore notation, if you use the names as defined either by related_name or the default related name.
I have a custom property in my models, and I would like to order_by using that property:
class Report(models.Model):
title = models.CharField(max_length = 255)
pub_date = models.DateTimeField('date published', blank=True, null=True)
status = models.CharField(max_length = 2, choices=REPORT_STATUS)
#property
def risk_rating(self):
# Calculate risk rating here
return risk_rating
I would like to do
reports = Report.objects.all().order_by('-risk_rating')
but of course it returns with a mysql error regarding the non-existent column. I would prefer not to recurse over calling this property in a sorting algorithm using python, but that is the only way I think I can get this to work.
reports = sorted(Report.objects.all(), key=lambda t: t.risk_rating)
Could I perhaps do better than that?
No, the ORM uses the database to order and cannot operate on a python value.
You can...
Calculate at database level via django's extra
Calculate the data and set it in a new column upon model save, then order by that.
Use python sorting
You could avoid the lambda with operator.itemgetter
import operator
reports = sorted(Report.objects.all(), key=itemgetter(risk_rating))
but I'm not sure if that's "better", perhaps you could explain how you would like the query improved?
class LineItemInline(admin.TabularInline):
model = LineItem
extra = 10
class InvoiceAdmin(admin.ModelAdmin):
model = Invoice
inlines = (LineItemInline,)
and
class LineItem(models.Model):
invoice = models.ForeignKey(Invoice)
item_product_code = models.CharField(max_length=32)
item_description = models.CharField(max_length=64)
item_commodity_code = models.ForeignKey(CommodityCode)
item_unit_cost = models.IntegerField()
item_unit_of_measure = models.ForeignKey(UnitOfMeasure, default=0)
item_quantity = models.IntegerField()
item_total_cost = models.IntegerField()
item_vat_amount = models.IntegerField(default=0)
item_vat_rate = models.IntegerField(default=0)
When I have it setup like this, the admin interface is requiring me to add data to all ten LineItems. The LineItems have required fields, but I expected it to not require whole line items if there was no data entered.
That's strange, it's supposed not to do that - it shouldn't require any data in a row if you haven't entered anything.
I wonder if the default options are causing it to get confused. Again, Django should cope with this, but try removing those and see what happens.
Also note that this:
item_unit_of_measure = models.ForeignKey(UnitOfMeasure, default=0)
is not valid, since 0 can not be the ID of a UnitOfMeasure object. If you want FKs to not be required, use null=True, blank=True in the field declaration.
Turns out the problem is default values. The one pointed out above about UnitOfMeasure isn't the actual problem though, any field with a default= causes it to require the rest of the data to be present. This to me seems like a bug since a default value should be subtracted out when determining if there is anything in the record that needs saving, but when I remove all the default values, it works.
In this code,
item_unit_of_measure = models.ForeignKey(UnitOfMeasure, default=0)
it was a sneaky way of letting the 0th entry in the database be the default value. That doesn't work unfortunately as he pointed out though.