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".
Related
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.
Goal was to implement a simple View for the Users to Select columns dynamically with some added calculated info (annotations on some specific columns) and also let them filter on fields.
Thankful for any comments, since this took me quite a few hours to get it working properly I thought I would provide a short writeup for anyone looking at a similar problem :)
Used Modules/Libraries etc:
Django-Filter
Django_Tables2
Bootstrap-Select to properly display Multiple Choice Fields
Example Model which we would like to use:
class Summary(models.Model):
billing_date = models.DateField(verbose_name='Billing Date')
period = models.CharField(max_length=10, verbose_name='Period')
operator = models.CharField(max_length=50, verbose_name='Operator')
product = models.CharField(max_length=30, verbose_name='Product')
...
The filters are really straightforward, the only special case here is that the we want an empty queryset initially and some fields should be required.
"info" will hold the select columns of our "Summary" Model, "product" and "operator" are just fields in Summary.
class AdHocReportFilter(django_filters.FilterSet):
info = django_filters.MultipleChoiceFilter(choices=report_field_choices, label='Available Fields', required=True)
product = django_filters.ModelChoiceFilter(queryset=Product.objects.all(), label='Products', required=True)
operator = django_filters.CharFilter(field_name="operator", lookup_expr='contains', label='Operator')
....
def __init__(self, *args, **kwargs):
super(AdHocReportFilter, self).__init__(*args, **kwargs)
if self.data == {}:
self.queryset = self.queryset.none()
Template:
Nothing interesting to show here, you can use Bootstrap-Select to tidy up your Multi Select Fields (there are quite a few nice writeups about that available).
Make sure to put your Table into an "if" as the object may or may not exist depending on your view (if someone wants an example of the template let me know)
View:
Extract your GET requests accordingly (either as list or simple value depending on your available filters).
The actual filter itself depends on what you want the user to be able to filter, make sure to either make all necessary fields required or replace them with some standard value as the filter will not accept None Types.
"field__contains" is your friend here since it will also show values on not selected fields!
Special Case, if there can actually be "Null" Values in the DB for specific fields, move them to another filter likethe below example of the Q - query!
Fortunately "values" accepts "*list" which is a simple list of all our available columns.
The annotations are just dependant on what you want to achieve.
Call the Table Object with the added argument "user_columns" which holds our list so we can build the required Table.
#login_required
def ad_hoc_report(request):
template_name = 'non_voice/ad_hoc_report.html'
filter = AdHocReportFilter(request.GET, queryset=Summary.objects.all())
info = request.GET.getlist('info', None)
product = request.GET.get('product', '')
operator = request.GET.get('operator', '')
start_date = request.GET.get('start_date', None)
end_date = request.GET.get('end_date', None)
if operator is None:
operator = ''
result_object = Summary.objects.filter(product__contains=product, ).filter((Q(operator__contains=operator)|Q(operator__isnull=True)).values(*info).annotate(
Amount=Sum("amount"), Count=Sum("count"))
table = AdHocReportTable(data=result_object, user_columns=info)
return render(request, template_name, {'filter': filter, 'table': table})
Table:
This was the difficult part and only possible with lots and lots of reading various stack overflow comments :)
First of all define your calculated annotation columns and set the required Meta info, the '...' is a built in placeholder without knowing the column name in advance (which helps us to move our calculated columns to the end of the Table)
In the init we first check if our "self.base_columns" are consistent with what we provided and remove columns which were deselected by our user, otherwise it would still show them empty even after filtering. (Maybe there is a nicer way to do this, haven't found it yet)
In the next step add the columns selected by our user dynamically from the mentioned above "user_columns" which we passed in the views.py
class AdHocReportTable(tables.Table):
Amount = tables.Column(verbose_name='Amount')
Count = tables.Column(verbose_name='Count')
class Meta:
# '...' is a built in placeholder!
sequence = ('...', 'Amount', 'Count')
template_name = "django_tables2/bootstrap4.html"
attrs = {'class': 'table table-hover', }
# This makes it possible to pass a dynamic list of columns to the Table Object
def __init__(self, data, user_columns, *args, **kwargs):
if user_columns:
calulated_columns = ['Amount', 'Count']
# Removes deselected columns from the table (otherwise they are shown empty)
for key, val in self.base_columns.items():
if key not in user_columns and key not in calulated_columns:
del self.base_columns[key]
# Add the Selected Columns dynamically to the Table
for col in user_columns:
self.base_columns[col] = tables.Column(verbose_name=col)
super(AdHocReportTable, self).__init__(data, user_columns, *args, **kwargs)
I have the following model
class MyModel(models.Model):
firstDate = models.DateTimeField(auto_now_add=True)
another = models.CharField(max_length=30)
I serialize it to JSON with
query = MyModel.objects.all()
data = serializers.serialize('json', query, use_natural_foreign_keys=True)
The DateTimeField returns the following format:
2017-12-19T22:50:04.328Z
The desired format is:
2017-12-19 22:50:04
Is there a simple way to achieve this without using the queryset.values() and queryset.extra() methods?
You'll want to create your own subclass of DjangoJSONEncoder - which is a subclass of Python's standard JSON encoder that implements (among other things) the functionality that you wish to change.:
If you look at the source code, it's pretty trivial. You'll want to change the section starting with:
if isinstance(o, datetime.datetime):
to have the method return the datetime in the format you desire.
class MyJSONEncoder(DjangoJSONEncoder):
def default(self, o):
if isinstance(o, datetime.datetime):
# Your implementation here
return super().default(o)
As suggested by aircraft, I treat it in the front-end with the JavaScript built-in Date implementation (didn't know about it until now). It's perfect for manipulating formats and locales.
In specific, I used this object function: Date.prototype.toLocaleFormat
I have a django form with a choicefield, where I dynamically load some choices into the field:
class EntryForm(forms.Form):
project = forms.ChoiceField()
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(EntryForm, self).__init__( *args, **kwargs)
CHOICES2=[]
for x in Project.objects.all() :
if user in x.users.all():
CHOICES2.append((x.name,x.name))
CHOICES1 = [(x.name,x.name) for x in Project.objects.all()]
print CHOICES2==CHOICES1 #this is True in this case
self.fields['project']=forms.ChoiceField(choices=CHOICES2)
The form is loaded into the template with {{form.as_table}}. The form does not show a dropdown for the project field.
Now the strange thing: if I change the last line to:
self.fields['project']=forms.ChoiceField(choices=CHOICES1)
it works, although the print statement of the "=="" comparison returns True (the lists are purposely the same - this is just for testing). I really have no idea how this can even work technically.
Edit - my project model:
class Project(BaseModel):
name = models.CharField(max_length=80)
users = models.ManyToManyField(User)
Your field named project already exists and there's no need to construct another one as you are doing. It's better to just set the choices on the existing field:
self.fields['project'].choices = CHOICES2
But maybe you'd be better off using a ModelChoiceField:
project = ModelChoiceField(queryset=Project.objects.none())
and then set the queryset you want in init like so:
self.fields['project'].queryset=Project.objects.filter(users__in=[user])
..which should give you a list of all projects associated with user.
I think you have to use queryset argument, which is mandatory:
https://docs.djangoproject.com/en/1.8/ref/forms/fields/#django.forms.ModelChoiceField.queryset
ChoiceField must be declared with (queryset=None), and in the __init__ method you complete the query:
https://docs.djangoproject.com/en/1.11/ref/forms/fields/#fields-which-handle-relationships
The problem could be about the execution order of the queries, or the cache of non-lazy queries.
And I agree with little_birdie: the field already exists.
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.