Incremented CharField - django

is there any possibility to define a Field which gets incremented, but starts with a letter? So the values should be like: S123, S124, S125.
I' d like to use the id field for it, but some reason i' d like to have it as a CharField.
As a workaround i could use:
id = models.CharField(max_length = 32, primary_key = True)
and redefine the save method, so i always precalculate its value, but this won' t be that robust than a "real" solution, and also my solution would too slow with the calculation.
Is there a proper solution for my problem?
Django: 1.9.2
Python: 3.4.2
.

I don't think you should manually define a primary key. Django usually uses relational database to build an app, which means it would rely on some key field to join other tables to do the lookup when it needs to. Having primary keys like S123 makes it extra hard to maintain because you need to store the same thing as a reference in other tables.
What I would suggest is storing the letter part and the digits separately. You could use the default id field django created as the digit part and create your own field to store the letter part. Then you would use a property method to return the value you want to have. Roughly:
class Foo(models.Model):
letter = models.CharField(max_length=1)
#property
def symbol(self):
return '%s%s' % (self.letter, self.id)
Then you could do:
foo = Foo.objects.create(letter='S')
print foo.symbol # this would print S1, S2, etc.
In case you don't know, here's an explanation of #property in python.

Related

Return object when aggregating grouped fields in Django

Assuming the following example model:
# models.py
class event(models.Model):
location = models.CharField(max_length=10)
type = models.CharField(max_length=10)
date = models.DateTimeField()
attendance = models.IntegerField()
I want to get the attendance number for the latest date of each event location and type combination, using Django ORM. According to the Django Aggregation documentation, we can achieve something close to this, using values preceding the annotation.
... the original results are grouped according to the unique combinations of the fields specified in the values() clause. An annotation is then provided for each unique group; the annotation is computed over all members of the group.
So using the example model, we can write:
event.objects.values('location', 'type').annotate(latest_date=Max('date'))
which does indeed group events by location and type, but does not return the attendance field, which is the desired behavior.
Another approach I tried was to use distinct i.e.:
event.objects.distinct('location', 'type').annotate(latest_date=Max('date'))
but I get an error
NotImplementedError: annotate() + distinct(fields) is not implemented.
I found some answers which rely on database specific features of Django, but I would like to find a solution which is agnostic to the underlying relational database.
Alright, I think this one might actually work for you. It is based upon an assumption, which I think is correct.
When you create your model object, they should all be unique. It seems highly unlikely that that you would have two events on the same date, in the same location of the same type. So with that assumption, let's begin: (as a formatting note, class Names tend to start with capital letters to differentiate between classes and variables or instances.)
# First you get your desired events with your criteria.
results = Event.objects.values('location', 'type').annotate(latest_date=Max('date'))
# Make an empty 'list' to store the values you want.
results_list = []
# Then iterate through your 'results' looking up objects
# you want and populating the list.
for r in results:
result = Event.objects.get(location=r['location'], type=r['type'], date=r['latest_date'])
results_list.append(result)
# Now you have a list of objects that you can do whatever you want with.
You might have to look up the exact output of the Max(Date), but this should get you on the right path.

Getting a value from a different class in django

As new to both Python and Django I have encounterd what I think is a simple problem, but which does not want to go away. I have a "table" with factors obtained from
class TjgFaktor(models.Model):
typ = models.CharField(max_length=2)
factor = models.FloatField()
Next I have another class which is foreign-keyed to this:
class Moment(models.Model):
typ = models.ForeignKey(TjgFaktor,on_delete=models.SET_NULL,null=True)
Now, what I want to do is to get the factor from the first class as an attribut to Moment. I have tried
def factor(self):
return TjgFaktor.objects.get(typ=self).factor
in the hope of getting the correct factor. However, when I do something like
person_moment = Moment.objects.all()
for e in person_moment:
print(e.factor())
what I get is "TjgFaktor matching query does not exist".
So how should I do this? I guess it is the function: it works if I replace type=self with pk=1.
You do not need to obtain the TjgFaktor through an explicit query. If you query for some_moment.typ, Django itself will perform an implcit query to fetch the TjgFaktor that corresponds to the Moment (through the foreign key), or None, if the foreign key is set to None.
We can thus query like:
def factor(self):
tjgfaktor = self.typ
if tjgfaktor:
return tjgfaktor.factor
In case there is no related TjgFaktor, then this function will return None as well.
In case you define a large amount of values, then this fetch might be inefficient: Django will fetch all columns from the database, and since we are only interested in a single one, this will thus result in some overhead.
We can avoid that by using the following query:
def factor(self):
if self.typ_id:
return (TjgFaktor.objects.values_list('factor', flat=True)
.get(pk=self.typ_id))
Assuming factor is function within Moment class, you can access factor if Moment object has related TjgFaktor object:
def factor(self):
return self.typ.factor if self.typ else None
So, the in the factor method, you need to enter the value for typ as a string value like this: A self does not satisfy the conditions of a string parameter that is required. You could do something like this -
def factor(self):
return TjgFaktor.objects.get(typ="YOUR_TYPE_IN_STRING").factor
def factor(self):
return TjgFaktor.objects.get(typ=self).factor
You can't actually compare the object of Moment with objects in TjgFaktor. You can directly access the values of parent model or foreignkey directly by doing like this.
e.typ.factor #this will directly give you factor values of foreign key.
Or you can compare with
def factor(self):
return TjgFaktor.objects.get(typ=self.typ.id).factor

Django get count of each age

I have this model:
class User_Data(AbstractUser):
date_of_birth = models.DateField(null=True,blank=True)
city = models.CharField(max_length=255,default='',null=True,blank=True)
address = models.TextField(default='',null=True,blank=True)
gender = models.TextField(default='',null=True,blank=True)
And I need to run a django query to get the count of each age. Something like this:
Age || Count
10 || 100
11 || 50
and so on.....
Here is what I did with lambda:
usersAge = map(lambda x: calculate_age(x[0]), User_Data.objects.values_list('date_of_birth'))
users_age_data_source = [[x, usersAge.count(x)] for x in set(usersAge)]
users_age_data_source = sorted(users_age_data_source, key=itemgetter(0))
There's a few ways of doing this. I've had to do something very similar recently. This example works in Postgres.
Note: I've written the following code the way I have so that syntactically it works, and so that I can write between each step. But you can chain these together if you desire.
First we need to annotate the queryset to obtain the 'age' parameter. Since it's not stored as an integer, and can change daily, we can calculate it from the date of birth field by using the database's 'current_date' function:
ud = User_Data.objects.annotate(
age=RawSQL("""(DATE_PART('year', current_date) - DATE_PART('year', "app_userdata"."date_of_birth"))::integer""", []),
)
Note: you'll need to change the "app_userdata" part to match up with the table of your model. You can pick this out of the model's _meta, but this just depends if you want to make this portable or not. If you do, use a string .format() to replace it with what the model's _meta provides. If you don't care about that, just put the table name in there.
Now we pick the 'age' value out so that we get a ValuesQuerySet with just this field
ud = ud.values('age')
And then annotate THAT queryset with a count of age
ud = ud.annotate(
count=Count('age'),
)
At this point we have a ValuesQuerySet that has both 'age' and 'count' as fields. Order it so it comes out in a sensible way..
ud = ud.order_by('age')
And there you have it.
You must build up the queryset in this order otherwise you'll get some interesting results. i.e; you can't group all the annotates together, because the second one for count depends on the first, and as a kwargs dict has no notion of what order the kwargs were defined in, when the queryset does field/dependency checking, it will fail.
Hope this helps.
If you aren't using Postgres, the only thing you'll need to change is the RawSQL annotation to match whatever database engine it is that you're using. However that engine can get the year of a date, either from a field or from its built in "current date" function..providing you can get that out as an integer, it will work exactly the same way.

Allocate ranges of primary key integers

Given RangeA of auto-incrementing primary key integers (1 to 32767) and RangeB (32768 to 2147483647). If a condition is true, save an object with a primary key assigned in RangeA, else save it in RangeB.
The admin (me) would be the only user saving to RangeA. If the above is not possible: It would be not ideal but still usable if Django always saved in RangeB and it required going into shell to save in RangeA.
How can this be done using Django and Postgres?
Quite possible. First thing is to change your model so that it doesn't use the standard AutoField as the primary key
class MyModel(models.Model):
id = models.IntegerField(primary_key=True)
Then you need to connect to postgresql and create two different sequences.
CREATE SEQUENCE small START 1;
CREATE SEQUENCE big START 32768;
Instead of typing that into the PSQL console, you might also consider editing the django migration (using a RunSQL directive) to create create these.
Next step is to override the save method
def save(self,*args, **kwargs)
if not self.id :
cursor = connection.cursor()
if small condition:
cursor.execute("select nextval('small')")
else:
cursor.execute("select nextval('big')")
self.id = cursor.fetchone()[0]
super(MyModel,self).save(*args,**kwargs)
Alternative to overriding the save method is to create a postgresql BEFORE INSERT Trigger. The round trip to get the nextval isn't very costly but if this is a concern creating a trigger is the option to choose. In that case you don't need to over ride the save method but you have to implement the same logic inside the trigger.

Django Models - Prepare the data for the database

I have a form that askes for a phone number. I need to make sure that only digits [0-9] get saved in the database.
In the Django documentation it says:
What happens when you save?
3) Prepare the data for the database. Each field is asked to provide its current value in a data type that can be written to the database.
How does this happen? Or more specifically, how can I make sure this is cleaned? I know that I can just override the models save method, but it seems like there is a better way and I'm just not sure how to do it.
I guess I could write a custom field for it, but that seems like overkill here.
Also, I realize that I can put the validation on the form, but it really feels like stripping out the characters belongs on the model.
Your question specifically about point 3 is a little different from "cleaning" in the way django uses the term.
3) Prepare the data for the database. Each field is asked to provide its current value in a data type that can be written to the database.
Point 3 is about converting the python object values to one suitable for a database. Specifically, this is done in Field.get_prep_value and Field.get_db_prep_value
https://docs.djangoproject.com/en/dev/howto/custom-model-fields/#django.db.models.Field.get_prep_value
It's the opposite of to_python which takes a DB value and converts it to a python object.
As for ensuring only digits 0-9 get stored, that would be done in a Fields clean method (subclass IntegerField), form clean method, form clean_FIELDNAME method, or model clean.
You can add a custom Form Cleaning method to your objects model - take a look at this article https://docs.djangoproject.com/en/dev/ref/forms/validation/#form-field-default-cleaning
Look at "Cleaning a specific field attribute"
use django model form + custom form field cleaning
Below is a quick example of what you might be looking for, where MyModel is the model containing the phone number field, which I named it tel here.
import re
class MyForm(ModelForm):
class Meta:
model = MyModel
def clean_tel(self):
tel = self.cleaned_data.get('tel', '') # this is from user input
# use regular expression to check if tel contains only digits; you might wanna enhance the regular expression to restrict the tel number to have certain number of digits.
result = re.match(r'\d+', tel)
if result:
return tel # tel is clean so return it
else:
raise ValidationError("Phone number contains invalid character.")