How to format DurationField input as min:sec:millisec Django - django

I want to force users to input lap times into a Form using the format min:sec:millisec (e.g. 00:00:000). I also want to display these times in this format in a DetailView but I want to store them as milliseconds to calculate personal bests and lap differences.
I have tried to set the default DurationField value as 01:01:001 but it displays in the format HH:MM:SS.MS
Here is my model:
class SwimTime(models.Model):
swimmer =models.ForeignKey(Swimmer, on_delete=models.CASCADE)
time = models.DurationField(_('Time'), default= timedelta(minutes=1, seconds=1, milliseconds=1))
distance = models.PositiveIntegerField(_('Distance'),null = False, default=50)
strokeType = models.CharField(_('Stroke Type'),max_length=20, choices=strokeTypes, default='FC')
date = models.DateField(_('Date Recorded'),default = timezone.now)
def save(self, *args, **kwargs):
self.full_clean()
return super().save(*args, **kwargs)

If you want to have an "example" you can use help_text option for the field showing the expected input format. You can use it as any other option: default, null...
help_text="Please use the following format: YYYY-MM-DD."
Anyway, this has nothing to do with how it will be rendered in any template or even in database or browser validation.
For templates you can use the Datetime formatting. Django has not built-in formatting as it has for date and time, but there are some projects that solve that. Also, in this question there are some good examples for writing your own filters and load them in the template.
Also, reading your data I guess that null=False is not necessary in 'distance' field: by default it will be set to False. And keep in mind that null=True and blank=True have different uses.

Related

Is there a way in Django to create and populate a column to the database with a string's length value?

I have a class/model in my models.py that can receive a textField. I want to add a column in my database that corresponds to the length of this textField. What is the best way of doing that?
You can add a field that stores the character count value to your model:
class YourModel(models.Model):
my_text_field = models.TextField()
char_count = models.DecimalField(max_digits=3, decimal_places=0, blank=True, null=True)
And override the model save method to update the field on save:
def save(self, *args, **kwargs):
if self.my_text_field:
self.char_count = len(self.my_text_field)
super().save(*args, **kwargs)
Adjust the field options (like max_digits) to suit your needs.
It depends how you want to use the length. The answer with the model property (#property) is good if you don't want to do complex queries involving the length, such as filtering by length, ordering, etc.
The solution with an extra database field solves the problem above, but it puts on you the duty of keeping the char_count column updated. If you need the length in your queries, a better way would be to use the database LENGTH function, which can be done readily in Django without changing your model. Examples, with a Article model that has a text CharField:
Order by article text length:
from django.db.models.functions import Length
sorted_qs = Article.objects.order_by(Length('text'))
If you print str(sorted_qs.query), you can see ORDER BY LENGTH("app_article"."text") ASC in the query.
Filter by length greater than x:
Article.objects.annotate(text_len=Length('text')).filter(text_len__gte=x)
See the doc on aggregation.
If you have a large database table, consider adding an index on the length. In PostgreSQL, that would be:
CREATE INDEX app_article_text_length_idx ON app_article (LENGTH(text));
You can define a function in your model that returns length of the TextField field.
Suppose your model is like this:
class ModelName(models.Model):
str_field = models.TextField()
#property
def length(self):
return (self.str_field)
And my_model is one object of this model class, below code returns length of str_field
my_model.length
This way doesn't add a column to database table but you can access the length of str_field.

Django - Custom values for inline list item field

After searching for a while, I can't seem to find any answer for this so I'm asking this question. What I want to is display some values based on a person's salary and a bracket. My models looks like this.
class Employee
salary = models.DecimalField
class Benefit
some_fields
class EmployeeBenefit
employee = models.ForeignKey
benefit = models.ForeignKey
class BenefitVersion
benefit = models.ForeignKey
class BenefitBracket
benefit_version = models.ForeignKey
from_salary = models.DecimalField
to_salary = models.DecimalField
the_fields_I_want_to_display
As you can see it's quite deep, there would be a bunch of querying to do to get to the fields I want.
I basically need to get the employee's salary(which is easy because this is gonna be inside EmployeeAdmin) then get the current benefit of the EmployeeBenefit list item, then based on the benefit and the employee's salary, get the bracket and then display some of it's fields on the inline.
I want to display the the_fields_I_want_to_display on the admin.TabularInline for EmployeeBenefit inside my EmployeeAdmin. I was testing using a forms.ModelForm in the inline and modifying it's contents using get_form based on this answer but django is not calling get_form. I also previously tried using calculated_fields but it's not being rendered as well.
I'm using django 3.1.7 btw
Edit:
I found this and try it out right now. I think it has potential but unfortunately the obj that get_formset gives is Employee so I still need to find a way to get the right EmployeeBenefit for the list item
After some more searching, I already figured out a way to do this. Based on this SO answer, I got an idea on how I should receive the data I passed to the formset using get_formset.
So, in my get_formset, I did something like this.
def get_formset(self, request, obj=None, **kwargs):
formset = super(Inline, self).get_formset(request, obj, **kwargs)
formset.request = request
benefit_details_dict_arr = []
emp_contribs = models.EmployeeBenefit.objects.filter(employee=obj)
#because what I'm getting here is Employee and not a EmployeeBenefit, I have to query it first
for contrib in emp_contribs:
bracket = contrib.get_benefit_bracket(datetime.now())
benefit_details_dict_arr.append({
"key": bracket.val,
"key2": bracket.val2,
})
formset.benefit_details = benefit_details_dict_arr
return formset
Then on the formset, I receive the data this way.
class EmpBenefitFormSet(forms.models.BaseInlineFormSet):
model = models.EmployeeBenefit
def __init__(self, *args, **kwargs):
super(EmpBenefitFormSet, self).__init__(*args, **kwargs)
cntr = 0
for form in self.forms:
init_data = self.benefit_details[cntr]
for field in form.fields:
if field in init_data:
form.initial[field] = init_data[field]
form.fields[field].required = False
form.fields[field].disabled = True
cntr += 1
Also, if you're gonna use this way of receiving the data, each field you want to inflate should be declared on the fields of the inline. This should work if the fields you want to set an initial value are actual fields of the model. You might need to tweak this a bit to work for custom fields

How to give choices from values of another field in Django models

I have a form, which is for scheduling an appointment. I give user 3 dates on which the meeting can be scheduled. Now in the admin I want to select one of the dates according to my convenience, and store it in a field of the same model. How can I do that
Right now my meeting dates are just char fields like this
schedule1 = models.CharField()
schedule2 = model.CharField()
schedule3 = models.CharFiedl()
selected_schedule = model.CharField(choices={something here})
The schedule fields will be filled when the object is created. So I am sure the choices will be there, I just have to dynamically set them. How can I do this?
Any help will be appreciated.
Here's what you do (if the schedule fields are already prefilled):
class ScheduleForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ScheduleForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance', None)
if instance is not None:
self.fields['selected_schedule'].choices = (
(instance.schedule1, instance.schedule1),
(instance.schedule2, instance.schedule2),
(instance.schedule3, instance.schedule3),
)
On your admin, simply state that you want to use that form:
class TheAdminInQuestion(admin.ModelAdmin):
form = ScheduleForm
Further note:
I'd recommend a different solution to your problem than storing the choices on the same model. For instance, you might have a model called ScheduleChoice, and there could be 3 records of that, etc. Or, you might calculate the value based on some other rules, and just don't store the choices at all. Also, I'd recommend using DateTimeField to store the dates. You can convert the date to any format you like (e.g. January 12th, 2011 at 3:35PM) and still store it as the same datetime object in the database.

django form datefield with runtime formatting

So, I have a form:
class FormBasicInfo(BasicForm):
valid_from = forms.DateField(required=False, input_formats=('%d/%m/%Y',), widget=DateInput(format='%d/%m/%Y'))
and I set the input and output formats. However, what if I want to set these formats at runtime, based on the date format preference of my user? how can that be done?
The way it is done above, the form will always validate against the European date format. Even if I specify more formats which is allowed, one of them will be first and take priority which means there will be cases when the validation will be done incorrectly.
You can override the __init__ method of the form class to customize the input_formats and widget. For e.g.
class FormBasicInfo(BasicForm):
....
def __init__(self, *args, **kwargs):
super(MForm, self).__init__(*args, **kwargs)
valid_from = self.fields['valid_from']
format = look_up_format_based_on_locale()
valid_from.input_formats = (format,)
valid_from.widget = forms.DateInput(format=format)
Where look_up_format_based_on_locale() is an abstraction for looking up the date format based on the user's locale. It should return an appropriate format string, say "%m/%d/%Y".

Django get_FIELD_display

I am trying to access data.get_age_display in my email template. I can't seem to get the display of this. I am not sure what I am doing wrong, I've using get_FIELD_display numerous times before but passed as context to a normal template. Is there something different with forms?
class RequestForm(forms.Form):
ADULT = 1
SENIOR = 2
STUDENT = 3
AGE_GROUP = (
(ADULT, 'Adult'),
(SENIOR, 'Senior'),
(STUDENT, 'Student'),
)
name = forms.CharField(max_length=255)
phone = forms.CharField(max_length=15)
age = forms.ChoiceField(choices=AGE_GROUP)
details = forms.CharField(widget=forms.Textarea())
def save(self):
order = Order(
name = self.cleaned_data['name'],
phone = self.cleaned_data['phone'],
age = self.cleaned_data['age'],
details = self.cleaned_data['details'],
)
order.save()
template = loader.get_template('request_email.txt')
# send over the order object in an email extracted so they can handle the ticket order
context = Context({
'data': order,
})
#import pdb; pdb.set_trace()
email_subject = 'Request Tickets'
mail_managers(email_subject, template.render(context))
in my request_email.txt all I am doing is {{ data.get_age_display }} any ideas?
Jeff
You haven't shown the code for the Order model that you're creating. Are you sure that the age field on the model has choices set?
Any reason you're not using a ModelForm? You're creating an Order object within the form's save() method, but not returning it. A modelform would do that for you, as well as removing the need to redeclare the fields for the form.
I know this is coming WAAAAAY later than the question being posted but here's my answer for completeness and anyone else who might benefit from it :-)
I'm going to assume that in AGE_GROUP, ADULT, SENIOR and STUDENT are integers. Your form cleaning will NOT automatically clean the string contained in the POST and return an integer. So in this code:
context = Context({
'data': order,
})
you would think order.age is referring to an integer but that is, in fact, incorrect. It's burned me a few times before because this will correctly save the integer to the physical table, but the order instance still has the string representation of the age field.
You could do one of two things:
1. Clean this in the field:
clean_age(self):
return int(self.cleaned_data['age'])
or create a new field type:
def MyChoiceField(forms.ChoiceField):
def clean(self, value):
if not value:
if self.required:
raise forms.ValidationError(self.error_messages['required'])
return None
else:
return None
return int(value)
link that to the form field:
age = MyChoiceField(choices=AGE_GROUP)
and then you'll be able to apply this logic to any other such choice field in future. Personally, I find the latter approach the best one and I stick all my custom field types into a form_utils file so that I can use them everywhere. Another gotcha is that forms.charField doesn't automatically strip the entered text and you can use this approach to fix that too.