In order to make a simple captacha-like field, I tried the following:
class CaptchaField(IntegerField):
def __init__(self, *args, **kwargs):
super(CaptchaField, self).__init__(*args, **kwargs)
self.reset()
def reset(self):
self.int_1 = random.randint(1, 10)
self.int_2 = random.randint(1, 10)
self.label = '{0} + {1}'.format(self.int_1, self.int_2)
def clean(self, value):
value = super(CaptchaField, self).clean(value)
if value != self.int_1 + self.int_2:
self.reset()
raise ValidationError(_("Enter the result"), code='captcha_fail')
return True
Every time my answer is wrong, the label is changed as expected but the test is performed against the first values of int_1 and int_2 and not against the newly randomly generated values.
I don't understand how Field object are created and why I can't access the values of my field.
Thanks in advance
Have a think about how this works in your view. When you render the form, the field is instantiated and sets the label to your random values, which is fine. Now, the user posts back to the view: what happens? Well, the form is instantiated again, as is the field, and the field is set to two new random values. Not surprisingly, this won't match up to the previous value, because you haven't stored that anywhere.
To do anything like this, you need to store state somewhere so it is preserved between requests. You could try putting it in the session, perhaps: or, a better way might be to hash the two values together and put them in a hidden field, then on submit hash the submitted value and compare it against the one in the hidden field. This would probably need to managed at the form level, not the field.
Related
I'm trying to print the result according to the user's age selection in the form, but my if,elif and else statements are not working.
class Quiz(models.Model):
age_choices = (('10-12', '10-12'),
('13-16', '13-16'),
('17-20', '17-20'),
('21-23','21-23'),
)
age = models.CharField(max_length = 100, choices = age_choices)
views.py
def create_order(request):
form = QuizForm(request.POST or None)
if request.method == 'POST':
quiz = Quiz.objects
if quiz.age=='10-12':
print("10-12")
elif quiz.age=='13-16':
print("13-16")
elif quiz.age=='17-20':
print("17-20")
elif quiz.age=='21-23':
print("21-23")
else:
return None
context = {'form':form}
return render(request, "manualupload.html", context)
quiz = Quiz.objects will return a django.db.models.manager.Manager object and this can be further used to fetch the objects from database belonging to that particular model. The appropriate query set will be quiz = Quiz.objects.all() Then you will get the list of all objects in that belong to Quiz model. Once you get list of all objects, you can get the specific object either by indexing or by filtering using a specific query that you need to look into and then for that particular object you can get the age property.
Refer to official django documentation about creating queries for more information.
As #Abhijeetk431 mentioned, your issue lies in quiz = Quiz.objects.
If you use type(quiz), you will find that it outputs django.db.models.manager.Manager. This is not what you want, as age is a property of the Quiz class, not the Manager class.
For starters, refer to this.
This will return you a Queryset list, something akin to an Excel table. age is akin to the column in the table. To get age, what you want is the row (the actual Quiz object) in said table, which you can achieve using get or using the square brackets [].
Thus, your code should look something like this:
Model.objects.all()[0]
That would return the correct object(only the first row) and allow you to get the column value.
However, further clarification will be needed though, to know exactly what your problem is aside from 'it doesn't work'. How did you know your code is not working; what did the debugger tell you?
I'm trying to take an object, look up a queryset, find the item in that queryset, and find the next one.
#property
def next_object_url(self):
contacts = Model.objects.filter(owner=self.owner).order_by('-date')
place_in_query = list(contacts.values_list('id', flat=True)).index(self.id)
next_contact = contacts[place_in_query + 1]
When I add this to the model and run it, here's what I get for each variable for one instance.
CURRENT = Current Object
NEXT = Next Object
contacts.count = 1114
self.id = 3533 #This is CURRENT.id
place_in_query = 36
contacts[place_in_query] = NEXT
next_contact = CURRENT
What am i missing / what dumb mistake am i making?
In your function, contacts is a QuerySet. The actual objets are not fetched in the line:
contacts = Model.objects.filter(owner=self.owner).order_by('-date')
because you don’t use a function like list(), you don’t iterate the QuerySet yet... It is evaluated later. This is probably the reason of your problem.
Since you need to search an ID in the list of contacts and the find the next object in that list, I think there is no way but fetch all the contact and use a classic Python loop to find yours objects.
#property
def next_object_url(self):
contacts = list(Model.objects.filter(owner=self.owner).order_by('-date').all())
for curr_contact, next_contact in zip(contacts[:-1], contacts[1:]):
if curr_contact.id == self.id:
return next_contact
else:
# not found
raise ContactNotFoundError(self.id)
Another solution would be to change your database model in order to add a notion of previous/next contact at database level…
I want to log every action what will be done with some SQLAlchemy-Models.
So, I have a after_insert, after_delete and before_update hooks, where I will save previous and current representation of model,
def keep_logs(cls):
#event.listens_for(cls, 'after_delete')
def after_delete_trigger(mapper, connection, target):
pass
#event.listens_for(cls, 'after_insert')
def after_insert_trigger(mapper, connection, target):
pass
#event.listens_for(cls, 'before_update')
def before_update_trigger(mapper, connection, target):
prev = cls.query.filter_by(id=target.id).one()
# comparing previous and current model
MODELS_TO_LOGGING = (
User,
)
for cls in MODELS_TO_LOGGING:
keep_logs(cls)
But there is one problem: when I'm trying to find model in before_update hook, SQLA returns modified (dirty) version.
How can I get previous version of model before updating it?
Is there a different way to keep model changes?
Thanks!
SQLAlchemy tracks the changes to each attribute. You don't need to (and shouldn't) query the instance again in the event. Additionally, the event is triggered for any instance that has been modified, even if that modification will not change any data. Loop over each column, checking if it has been modified, and store any new values.
#event.listens_for(cls, 'before_update')
def before_update(mapper, connection, target):
state = db.inspect(target)
changes = {}
for attr in state.attrs:
hist = attr.load_history()
if not hist.has_changes():
continue
# hist.deleted holds old value
# hist.added holds new value
changes[attr.key] = hist.added
# now changes map keys to new values
I had a similar problem but wanted to be able to keep track of the deltas as changes are made to sqlalchemy models instead of just the new values. I wrote this slight extension to davidism's answer to do that along with slightly better handling of before and after, since they are lists sometimes or empty tuples other times:
from sqlalchemy import inspect
def get_model_changes(model):
"""
Return a dictionary containing changes made to the model since it was
fetched from the database.
The dictionary is of the form {'property_name': [old_value, new_value]}
Example:
user = get_user_by_id(420)
>>> '<User id=402 email="business_email#gmail.com">'
get_model_changes(user)
>>> {}
user.email = 'new_email#who-dis.biz'
get_model_changes(user)
>>> {'email': ['business_email#gmail.com', 'new_email#who-dis.biz']}
"""
state = inspect(model)
changes = {}
for attr in state.attrs:
hist = state.get_history(attr.key, True)
if not hist.has_changes():
continue
old_value = hist.deleted[0] if hist.deleted else None
new_value = hist.added[0] if hist.added else None
changes[attr.key] = [old_value, new_value]
return changes
def has_model_changed(model):
"""
Return True if there are any unsaved changes on the model.
"""
return bool(get_model_changes(model))
If an attribute is expired (which sessions do by default on commit) the old value is not available unless it was loaded before being changed. You can see this with the inspection.
state = inspect(entity)
session.commit()
state.attrs.my_attribute.history # History(added=None, unchanged=None, deleted=None)
# Load history manually
state.attrs.my_attribute.load_history()
state.attrs.my_attribute.history # History(added=(), unchanged=['my_value'], deleted=())
In order for attributes to stay loaded you can not expire entities by settings expire_on_commit to False on the session.
The idea of the code below should be that it only fires if the field verification_pin is empty i.e. on a new record. However, it seems that every time I save the model it generates a new pin ignoring if instance.verification_pin is None statement, why, what have I missed?
#receiver(pre_save, sender=CompanyUser)
def my_callback(sender, instance, *args, **kwargs):
if instance.verification_pin is None:
instance.verification_pin = instance.generate_pin()
instance.is_active = False
instance.send_verification_pin()
Model:
class CompanyUser(User):
verification_pin = models.IntegerField(max_length=4, null=True)
objects = UserManager()
def generate_pin(self):
"""
Returns a random four digit pin.
"""
return random.randint(999, 9999)
def send_verification_pin(self):
self.email_user(
subject="Test",
message="Your pin: %s" % self.verification_pin,
from_email=settings.DEFAULT_FROM_EMAIL
)
You can set the default value for a field to a callable object.
Or, you might try using a post_save handler instead, where you can check if created is True. Also, it might be helpful to check the value in verification_pin after saving, to see if it really got set or not.
I'm trying to create a custom timestamp field.
class TimestampKey(models.CharField):
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
import time
kwargs['unique'] = True
kwargs['max_length'] = 20
kwargs['auto_created'] = True
kwargs['editable']=False
super(TimestampKey, self).__init__(*args, **kwargs)
def to_python(self, value) :
return value
def get_db_prep_value(self, value) :
try:
import time
t = time.localtime()
value = reduce(lambda a,b:str(a)+str(b),t)
except ValueError:
value = {}
return value
class Table1(models.Model):
f = TimestampKey(primary_key=True)
n = ....
It stores the value with appropriate timestamp in the db. But it doesnt populate the field 'f' in the object.
Eg:
t1 = Table1(n="some value")
t1.f -> blank
t1.save()
t1.f -> blank.
This is the problem. Am I missing something so that it doesnt populate the filed?
Please shed some light on this.
Thanks.
Is it wise to use a timestamp as your primary key? If your database uses ISO 8601 or really any time format in which second is the smallest time interval... Well, anyway, my point is that you have no guarantee, especially if this is going to be a web-facing application that two entries are going to resolve within the minimum time interval. That is, if the smallest time interval is a second, as in ISO 8601, if you get two requests to save in the same second, you're going to get an error condition. Why not stick to automatically incrementing integer keys and just make the timestamp its own field?
The get_db_prep_value method only prepares a value for the database, but doesn't send the prepared value back to the Python object in any way. For that you would need the pre_save method, I think.
Fortunately, there's already an "auto_now" option on DateField and DateTimeField that does what you want, using pre_save. Try:
class Table1(models.Model):
f = models.DateTimeField(auto_now=True)
(If you must write your own pre_save, look at how auto_now modifies the actual model instance in /django/db/models/fields/__init__.py on lines 486-492:
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
value = datetime.datetime.now()
setattr(model_instance, self.attname, value)
return value
else:
return super(DateField, self).pre_save(model_instance, add)
)