I need to skip validation of the OrderAmount field but for it to still save the invalidated data. Is there a way this can be done? I know django allows you to make your own validation, but I don't know how to make it completely skip just one field's validation.
model:
class LiquorOrder(models.Model):
pack_size = (
('1', '1'),
('2', '2'),
)
LiquorOrderID = models.AutoField(primary_key=True)
storeliquorID = models.ForeignKey(StoreLiquor)
orderID = models.ForeignKey(Order)
OrderAmount = models.PositiveSmallIntegerField('Order Amount', max_length=3, choices=pack_size)
TotalPrice = models.DecimalField('Total Price', max_digits=5, decimal_places=2)
Form:
class AddToOrderForm(forms.ModelForm):
class Meta:
model = LiquorOrder
fields = ('OrderAmount',)
For a PositiveSmallIntegerField the only validation Django does is ensure the value is indeed a positive integer within the appropriate range. If you were to skip this, you would run into problems when Django tries to write the value to your database. If you were to, say, try to write the value "marshmallows" to a DB column that's expecting an integer, the DB will throw errors and Django will turn around and throw you an IntegrityError.
If you really wanted to try, you could override the field to be CharField with required set to False (basically allowing any keyboard input):
class AddToOrderForm(forms.ModelForm):
OrderAmount = forms.CharField(required=False)
Django would then return True when you run is_valid(), but throw errors when you try to call save().
It sounds like your real issue is with your model not matching your current project requirements. If that is the case, look into migrating to a new model. The Python library South is brilliant tool for this purpose and is used heavily by the Django community. I would read up on DB migrations and see if you can come up with a solution that way.
Related
I was using django-hitcont to count the views on my Post model. I am trying to get the most viewed post in my ListView using this query objects.order_by('hit_count_generic__hits') and it is working fine on SQLite but on PostgreSQL, it is giving me this error :
django.db.utils.ProgrammingError: operator does not exist: integer = text LINE 1: ...R JOIN "hitcount_hit_count" ON ("posts_post"."id" = "hitcoun....
models.py
class Post(models.Model, HitCountMixin):
author = models.ForeignKey(User, related_name='authors', on_delete=models.CASCADE)
title = models.CharField('Post Title', max_length = 150)
description = models.TextField('Description', max_length=1000, blank = True)
date_posted = models.DateTimeField('Date posted', default = timezone.now)
date_modifed = models.DateTimeField('Date last modified', default = timezone.now)
document = models.FileField('Document of Post', upload_to='documents', \
validators=[FileExtensionValidator(allowed_extensions = ['pdf', 'docx']), validate_document_size] \
)
hit_count_generic = GenericRelation(
HitCount,
object_id_field='object_pk',
related_query_name='hit_count_generic_relation'
)
views.py
queryset = Post.objects.order_by('hit_count_generic__hits')
I found this issue on Github related to the problem, but I am still not able to figure out the mentioned workaround.
When comparing different types (in this example integer and text), equals operator throws this exception. To fix that, convert HitCount model pk field to integer and you are good to go. To do that, you need to create and apply migration operation. Django is a really good framework to handle this kind of operations. You just need to check values are not null and are "convertable" to integer. Just change the field type and run two commands below.
python manage.py makemigrations
python manage.py migrate
Before updating your model, I highly recommend you to take a backup in case of failure. This is not an easy operation but you can follow the these links to understand what is going on during this the process.
migrations dump and restore initial data
If you don't care the data on table, just drop table and create a brand new migration file and recreate table.
In the official Django 2 tutorial I found this:
from django.db import models
class Student(models.Model):
FRESHMAN = 'FR'
SOPHOMORE = 'SO'
JUNIOR = 'JR'
SENIOR = 'SR'
YEAR_IN_SCHOOL_CHOICES = (
(FRESHMAN, 'Freshman'),
(SOPHOMORE, 'Sophomore'),
(JUNIOR, 'Junior'),
(SENIOR, 'Senior'),
)
year_in_school = models.CharField(
max_length=2,
choices=YEAR_IN_SCHOOL_CHOICES,
default=FRESHMAN,
)
Now my question is does using choices mean that only either of the four defined values is valid for this field? If so what is the use of specifying the max_length? If not, why not use a validator that validates if the value is actually exacly one of the specified ones or at least a validator that only accepts an specific length not just an upper bound.
The max_length is enforced at the database level, but the choices are enforced at python code level (when calling full_clean() or clean_<fieldname>()).
They are independent of each other.
If you set a value to your field other than the specified choices and you don't call instance.full_clean() or instance.clean_<fieldname>(), it could still get saved to the database without raising errors.
But if you use djangos forms, the choices validation is done for you (the form calls full_clean()) and you don't need to worry about it.
This means, for example, if you set max_length smaller than your largest option in choices, your database will silently truncate the values for that field or raise a DatabaseError; either way you will not get it to work.
This separation is useful, for example, if you want to add more choices later; if the new options are not larger than max_length, there will be no need to to change the database structure (that means, the new migration will NOT issue alter table SQL statements).
I ran into this issue recently, except that I wasn't just using a two letter code so it was a bit tedious to make sure I had a valid max_length. I ended up doing something like this:
year_in_school = models.CharField(
max_length=max(len(v[0]) for v in YEAR_IN_SCHOOL_CHOICES),
choices=YEAR_IN_SCHOOL_CHOICES,
default=FRESHMAN,
)
If I ever add an option that exceeds the existing max length, makemigrations will detect that and add that change to the migration.
I found it convenient to extend TextChoices with a property that can be used to set max_length:
TextChoicesMeta = type(models.TextChoices)
class ExtendedTextChoicesMeta(TextChoicesMeta):
#property
def max_length(cls) -> int:
return max(len(value) for value in cls.values)
class ExtendedTextChoices(models.TextChoices, metaclass=ExtendedTextChoicesMeta):
pass
class PropertyType(ExtendedTextChoices):
RESIDENTIAL = "RESIDENTIAL", _("residential")
COMMERCIAL = "COMMERCIAL", _("commercial")
INDUSTRIAL = "INDUSTRIAL", _("industrial")
RAW_LAND = "RAW_LAND", _("raw land")
SPECIAL_USE = "SPECIAL_USE", _("special use")
class Property(models.Model):
property_type = models.CharField(
choices=PropertyType.choices,
max_length=PropertyType.max_length,
)
There are some constraints to this problem. We currently use a production database, with live Virtual Machine Statistics. We are trying to create a django interface, that interfaces with the tables we want our administrators to be able to edit. Thus, migrations are out of the question, for unless I have come to understand migrations wrong it will affect the current database structure and or data.
I matched the database structure exactly in my models.py file. However I have run into a few issues. One of the issues I have run into is when I try to add a new item under the admin control panel it will give me an integrity error as it is attempting to insert a null value for the field I have set as the primary key in the models.py file.
We are currently using an oracle database.
My Models.py not all of it but a sample of it.
class License(models.Model):
license_id = models.AutoField(primary_key = True, editable = False, db_column='license_id')
license_authority_id = models.ForeignKey(License_authoritie, on_delete = models.PROTECT, db_column='license_authority_id')
product = models.CharField(max_length = 20)
class Meta:
managed = False
db_table = 'licenses'
ordering = ['product']
def __unicode__(self): # Python 3: def __str__(self):
return self.product
class Vm_license(models.Model):
vm_license_id = models.AutoField(primary_key = True, db_column='vm_license_id')
vm_id = models.ForeignKey(Vm, on_delete = models.PROTECT, db_column='vm_id')
license = models.ManyToManyField(License)
class Meta:
managed = False
db_table = 'vm_licenses'
The error I get:
Request Method: POST
Request URL: http://127.0.0.1:8000/admin/portal/vm_license/add/
Django Version: 1.6.5
Exception Type: IntegrityError
Exception Value:
ORA-01400: cannot insert NULL into ("DEV"."VM_LICENSES"."VM_LICENSE_ID")
On top of that I have run into another problem.
For these two tables, under the vm_licenses section in the admin panel which is a table that holds all VM's and their assigned licenses. I need the ability to select multiple licenses at a time for each vm_id under the add section of the admin panel but i'm not quite sure how to do this.
admin.py code
class vm_license_admin(admin.ModelAdmin):
#list_display = ('vm_id', 'license_id')
list_display = ('vm_id',)
search_fields = ('vm_id__vm_name',)
ordering = ('vm_id',)
filter_horizontal = ('license',)
admin.site.register(Vm_license, vm_license_admin)
I also made an oracle trigger to auto increment a primary key if there is none, but im still getting the same error.
CREATE OR REPLACE TRIGGER license_trigger
BEFORE INSERT ON vm_licenses
FOR EACH ROW
BEGIN
SELECT vm_license_seq.nextval
INTO :new.vm_license_id
FROM dual;
END;
to be more percise I am using a manytomany field and it displays correctly when I goto add a new item before clicking save and getting the null error, however if I goto an existing item it will say table or view doesnt exist.
I was going to comment on your question, but I do not have the reputation yet...
but can I suggest you post your relevant admin.py code? Perhaps there is something within it relating to the Null PK error.
With regards to the second part, a ManyToManyField sounds more suitable.
I am trying to add an additional custom field to a django model. I have been having quite a hard time figuring out how to do the following, and I will be awarding a 150pt bounty for the first fully correct answer when it becomes available (after it is available -- see as a reference Improving Python/django view code).
I have the following model, with a custom def that returns a video count for each user --
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
positions = models.ManyToManyField('Position', through ='PositionTimestamp', blank=True)
def count(self):
from django.db import connection
cursor = connection.cursor()
cursor.execute(
"""SELECT (
SELECT COUNT(*)
FROM videos_video v
WHERE v.uploaded_by_id = p.id
OR EXISTS (
SELECT NULL
FROM videos_videocredit c
WHERE c.video_id = v.id
AND c.profile_id = p.id
)
) AS Total_credits
FROM userprofile_userprofile p
WHERE p.id = %d"""%(int(self.pk))
)
return int(cursor.fetchone()[0])
I want to be able to order by the count, i.e., UserProfile.objects.order_by('count'). Of course, I can't do that, which is why I'm asking this question.
Previously, I tried adding a custom model Manager, but the problem with that was I also need to be able to filter by various criteria of the UserProfile model: Specifically, I need to be able to do: UserProfile.objects.filter(positions=x).order_by('count'). In addition, I need to stay in the ORM (cannot have a raw sql output) and I do not want to put the filtering logic into the SQL, because there are various filters, and would require several statements.
How exactly would I do this? Thank you.
My reaction is that you're trying to take a bigger bite than you can chew. Break it into bite size pieces by giving yourself more primitives to work with.
You want to create these two pieces separately so you can call on them:
Does this user get credit for this video? return boolean
For how many videos does this user get credit? return int
Then use a combination of #property, model managers, querysets, and methods that make it easiest to express what you need.
For example you might attach the "credit" to the video model taking a user parameter, or the user model taking a video parameter, or a "credit" manager on users which adds a count of videos for which they have credit.
It's not trivial, but shouldn't be too tricky if you work for it.
"couldn't you use something like the "extra" queryset modifier?"
see the docs
I didn't put this in an answer at first because I wasn't sure it would actually work or if it was what you needed - it was more like a nudge in the (hopefully) right direction.
in the docs on that page there is an example
query
Blog.objects.extra(
select={
'entry_count': 'SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id'
},
)
resulting sql
SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id) AS entry_count
FROM blog_blog;
Perhaps doing something like that and accessing the user id which you currently have as p.id as appname_userprofile.id
note:
Im just winging it so try to play around a bit.
perhaps use the shell to output the query as sql and see what you are getting.
models:
class Positions(models.Model):
x = models.IntegerField()
class Meta:
db_table = 'xtest_positions'
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
positions = models.ManyToManyField(Positions)
class Meta:
db_table = 'xtest_users'
class Video(models.Model):
usr = models.ForeignKey(UserProfile)
views = models.IntegerField()
class Meta:
db_table = 'xtest_video'
result:
test = UserProfile.objects.annotate(video_views=Sum('video__views')).order_by('video_views')
for t in test:
print t.video_views
doc: https://docs.djangoproject.com/en/dev/topics/db/aggregation/
This is either what you want, or I've completely misunderstood!.. Anywhoo... Hope it helps!
I want to show the human-readable name for the type selected but I
keep getting the stored value.
TYPE_CHOICES = (
('0', 'Basic'),
('1', 'Full'),
('2', 'Intermediate'),
)
class ServiceType(models.Model):
type = models.IntegerField(max_length=1, choices=TYPE_CHOICES)
amount = models.DecimalField(max_digits=10, decimal_places=2)
def __unicode__(self):
return '%s' % (self.get_type_display())
It seems that you have your answer, but as another link, I'd just like to point out James Bennett's thoughts on this:
Handle choices the right way
I think it is a pretty convenient way to do things, and removes the 'magic number' aspect of things. Worth a read IMO, even if you go for a different option.
From his article (quoted in case it disappears):
class Entry(models.Model):
LIVE_STATUS = 1
DRAFT_STATUS = 2
HIDDEN_STATUS = 3
STATUS_CHOICES = (
(LIVE_STATUS, 'Live'),
(DRAFT_STATUS, 'Draft'),
(HIDDEN_STATUS, 'Hidden'),
)
# ...some other fields here...
status = models.IntegerField(choices=STATUS_CHOICES, default=LIVE_STATUS)
Now we can just import the Entry model and query like so:
live_entries = Entry.objects.filter(status=Entry.LIVE_STATUS)
draft_entries = Entry.objects.filter(status=Entry.DRAFT_STATUS)
You probably want to use ChoiceField instead of IntegerField in your model. It sounds like you are seeing an input tag with type=text in your admin but want a select tag. The default widget associated with a IntegerField is TextInput which would explain what you are seeing.
Another option is to write your own admin and explicitly call out that you want type to be a ChoiceField in the admin. Something like this:
class ServiceTypeAdmin(admin.ModelAdmin):
# ...
type = fields.ChoiceField(choices=TYPE_CHOICES)
admin.site.register(ServiceType, ServiceTypeAdmin)
I would personally start by switching the IntegerField to a ChoiceField. Way less work involved.
I had that same problem, and couldn't figure out why it works, but if you change the field type to CharField the get_type_display should work fine.
TYPE_CHOICES = (
('B', 'Basic'),
('F', 'Full'),
('I', 'Intermediate'),
)
class ServiceType(models.Model):
type = models.CharField(max_length=1, choices=TYPE_CHOICES)
amount = models.DecimalField(max_digits=10, decimal_places=2)
Rookie mistake, I've changed the tuple values from ('0', 'Basic) to (0, 'Basic') and it worked. I didn't realize that I was saving a char value as an integer value.
Thanks for your help.
Trick
use TypedChoiceField()
The answer of your questoin lies in using TypedChoiceField, not ChoiceField.
you are getting type field from a django form, using the cleaned_data from a ChoiceField. The problem with this is that the output from a ChoiceField is a string, not an integer.
if you use get_type_display() right after saving the form, u would probably get the value, but when u have try to retrieve the value from the DB, you would get integer instead of string(because your are saving type as Integer field), here you wont be able to get value with get_type_display.
Having now looked into this, I see that you should have used the TypedChoiceField, to ensure that the output from cleaned_data is always an integer or string.
first of all change IntergerField to Char field or SmallIntergetField.
Hope this helps.
Code
type = models.SmallIntegerField(choices=TYPE_CHOICES)
in forms.py
type = TypedChoiceField(coerce=int, required=False, empty_value=0, choices=TYPE_CHOICES)
another possibility is that you could use MODELFORM and provide the widgets for the field.
Forms.py
class abc(forms.Modelform)
class Meta:
model = FOO
widgets = {
'type': forms.TypedChoiceField(coerce=int, required=False, empty_value=0, choices=TYPE_CHOICES),
type shadows with an in-built bulletin, that's why get_type_display() has no effects. Avoid using column names as type instead use service_type or something else and use get_service_type_display() i.e get_<column_name>_display().