mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-10-31 15:50:03 +01:00 
			
		
		
		
	Use custom inputs for date picker and amounts
This commit is contained in:
		| @@ -2,11 +2,15 @@ | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django import forms | ||||
|  | ||||
| from activity.models import Activity | ||||
| from note_kfet.inputs import DateTimePickerInput | ||||
|  | ||||
|  | ||||
| class ActivityForm(forms.ModelForm): | ||||
|     class Meta: | ||||
|         model = Activity | ||||
|         fields = '__all__' | ||||
|         widgets = { | ||||
|             "date_start": DateTimePickerInput(), | ||||
|             "date_end": DateTimePickerInput(), | ||||
|         } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib.auth.mixins import LoginRequiredMixin | ||||
| from django.views.generic import CreateView, DetailView, UpdateView, TemplateView | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django_tables2.views import SingleTableView | ||||
| @@ -9,12 +10,12 @@ from .forms import ActivityForm | ||||
| from .models import Activity | ||||
|  | ||||
|  | ||||
| class ActivityCreateView(CreateView): | ||||
| class ActivityCreateView(LoginRequiredMixin, CreateView): | ||||
|     model = Activity | ||||
|     form_class = ActivityForm | ||||
|  | ||||
|  | ||||
| class ActivityListView(SingleTableView): | ||||
| class ActivityListView(LoginRequiredMixin, SingleTableView): | ||||
|     model = Activity | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
| @@ -25,14 +26,14 @@ class ActivityListView(SingleTableView): | ||||
|         return ctx | ||||
|  | ||||
|  | ||||
| class ActivityDetailView(DetailView): | ||||
| class ActivityDetailView(LoginRequiredMixin, DetailView): | ||||
|     model = Activity | ||||
|  | ||||
|  | ||||
| class ActivityUpdateView(UpdateView): | ||||
| class ActivityUpdateView(LoginRequiredMixin, UpdateView): | ||||
|     model = Activity | ||||
|     form_class = ActivityForm | ||||
|  | ||||
|  | ||||
| class ActivityEntryView(TemplateView): | ||||
| class ActivityEntryView(LoginRequiredMixin, TemplateView): | ||||
|     pass | ||||
|   | ||||
| @@ -18,10 +18,5 @@ def pretty_money(value): | ||||
|         ) | ||||
|  | ||||
|  | ||||
| def cents_to_euros(value): | ||||
|     return "{:.02f}".format(value / 100) if value else "" | ||||
|  | ||||
|  | ||||
| register = template.Library() | ||||
| register.filter('pretty_money', pretty_money) | ||||
| register.filter('cents_to_euros', cents_to_euros) | ||||
|   | ||||
| @@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _ | ||||
| from django.views.generic import CreateView, UpdateView | ||||
| from django_tables2 import SingleTableView | ||||
| from django.urls import reverse_lazy | ||||
| from note_kfet.inputs import AmountInput | ||||
| from permission.backends import PermissionBackend | ||||
|  | ||||
| from .forms import TransactionTemplateForm | ||||
| @@ -40,6 +41,7 @@ class TransactionCreateView(LoginRequiredMixin, SingleTableView): | ||||
|         """ | ||||
|         context = super().get_context_data(**kwargs) | ||||
|         context['title'] = _('Transfer money') | ||||
|         context['amount_widget'] = AmountInput(attrs={"id": "amount"}) | ||||
|         context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk | ||||
|         context['special_polymorphic_ctype'] = ContentType.objects.get_for_model(SpecialTransaction).pk | ||||
|         context['special_types'] = NoteSpecial.objects.order_by("special_type").all() | ||||
|   | ||||
| @@ -7,6 +7,7 @@ from crispy_forms.helper import FormHelper | ||||
| from crispy_forms.layout import Submit | ||||
| from django import forms | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from note_kfet.inputs import DatePickerInput, AmountInput | ||||
|  | ||||
| from .models import Invoice, Product, Remittance, SpecialTransactionProxy | ||||
|  | ||||
| @@ -19,7 +20,7 @@ class InvoiceForm(forms.ModelForm): | ||||
|     # Django forms don't support date fields. We have to add it manually | ||||
|     date = forms.DateField( | ||||
|         initial=datetime.date.today, | ||||
|         widget=forms.TextInput(attrs={'type': 'date'}) | ||||
|         widget=DatePickerInput() | ||||
|     ) | ||||
|  | ||||
|     def clean_date(self): | ||||
| @@ -30,12 +31,21 @@ class InvoiceForm(forms.ModelForm): | ||||
|         exclude = ('bde', ) | ||||
|  | ||||
|  | ||||
| class ProductForm(forms.ModelForm): | ||||
|     class Meta: | ||||
|         model = Product | ||||
|         fields = '__all__' | ||||
|         widgets = { | ||||
|             "amount": AmountInput() | ||||
|         } | ||||
|  | ||||
|  | ||||
| # Add a subform per product in the invoice form, and manage correctly the link between the invoice and | ||||
| # its products. The FormSet will search automatically the ForeignKey in the Product model. | ||||
| ProductFormSet = forms.inlineformset_factory( | ||||
|     Invoice, | ||||
|     Product, | ||||
|     fields='__all__', | ||||
|     form=ProductForm, | ||||
|     extra=1, | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -50,18 +50,8 @@ class InvoiceCreateView(LoginRequiredMixin, CreateView): | ||||
|     def form_valid(self, form): | ||||
|         ret = super().form_valid(form) | ||||
|  | ||||
|         kwargs = {} | ||||
|  | ||||
|         # The user type amounts in cents. We convert it in euros. | ||||
|         for key in self.request.POST: | ||||
|             value = self.request.POST[key] | ||||
|             if key.endswith("amount") and value: | ||||
|                 kwargs[key] = str(int(100 * float(value))) | ||||
|             elif value: | ||||
|                 kwargs[key] = value | ||||
|  | ||||
|         # For each product, we save it | ||||
|         formset = ProductFormSet(kwargs, instance=form.instance) | ||||
|         formset = ProductFormSet(self.request.POST, instance=form.instance) | ||||
|         if formset.is_valid(): | ||||
|             for f in formset: | ||||
|                 # We don't save the product if the designation is not entered, ie. if the line is empty | ||||
| @@ -112,16 +102,7 @@ class InvoiceUpdateView(LoginRequiredMixin, UpdateView): | ||||
|     def form_valid(self, form): | ||||
|         ret = super().form_valid(form) | ||||
|  | ||||
|         kwargs = {} | ||||
|         # The user type amounts in cents. We convert it in euros. | ||||
|         for key in self.request.POST: | ||||
|             value = self.request.POST[key] | ||||
|             if key.endswith("amount") and value: | ||||
|                 kwargs[key] = str(int(100 * float(value))) | ||||
|             elif value: | ||||
|                 kwargs[key] = value | ||||
|  | ||||
|         formset = ProductFormSet(kwargs, instance=form.instance) | ||||
|         formset = ProductFormSet(self.request.POST, instance=form.instance) | ||||
|         saved = [] | ||||
|         # For each product, we save it | ||||
|         if formset.is_valid(): | ||||
|   | ||||
							
								
								
									
										280
									
								
								note_kfet/inputs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								note_kfet/inputs.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,280 @@ | ||||
| # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| """ | ||||
| This file comes from the project `django-bootstrap-datepicker-plus` available on Github: | ||||
| https://github.com/monim67/django-bootstrap-datepicker-plus | ||||
| This is distributed under Apache License 2.0. | ||||
|  | ||||
| This adds datetime pickers with bootstrap. | ||||
| """ | ||||
|  | ||||
| """Contains Base Date-Picker input class for widgets of this package.""" | ||||
|  | ||||
| from json import dumps as json_dumps | ||||
|  | ||||
| from django.forms.widgets import DateTimeBaseInput, NumberInput | ||||
|  | ||||
|  | ||||
| class AmountInput(NumberInput): | ||||
|     """ | ||||
|     This input type lets the user type amounts in euros, but forms receive data in cents | ||||
|     """ | ||||
|     template_name = "note/amount_input.html" | ||||
|  | ||||
|     def format_value(self, value): | ||||
|         return None if value is None or value == "" else "{:.02f}".format(value / 100, ) | ||||
|  | ||||
|     def value_from_datadict(self, data, files, name): | ||||
|         val = super().value_from_datadict(data, files, name) | ||||
|         return str(int(100 * float(val))) if val else val | ||||
|  | ||||
|  | ||||
| class DatePickerDictionary: | ||||
|     """Keeps track of all date-picker input classes.""" | ||||
|  | ||||
|     _i = 0 | ||||
|     items = dict() | ||||
|  | ||||
|     @classmethod | ||||
|     def generate_id(cls): | ||||
|         """Return a unique ID for each date-picker input class.""" | ||||
|         cls._i += 1 | ||||
|         return 'dp_%s' % cls._i | ||||
|  | ||||
|  | ||||
| class BasePickerInput(DateTimeBaseInput): | ||||
|     """Base Date-Picker input class for widgets of this package.""" | ||||
|  | ||||
|     template_name = 'bootstrap_datepicker_plus/date_picker.html' | ||||
|     picker_type = 'DATE' | ||||
|     format = '%Y-%m-%d' | ||||
|     config = {} | ||||
|     _default_config = { | ||||
|         'id': None, | ||||
|         'picker_type': None, | ||||
|         'linked_to': None, | ||||
|         'options': {}  # final merged options | ||||
|     } | ||||
|     options = {}  # options extended by user | ||||
|     options_param = {}  # options passed as parameter | ||||
|     _default_options = { | ||||
|         'showClose': True, | ||||
|         'showClear': True, | ||||
|         'showTodayButton': True, | ||||
|         "locale": "fr", | ||||
|     } | ||||
|  | ||||
|     # source: https://github.com/tutorcruncher/django-bootstrap3-datetimepicker | ||||
|     # file: /blob/31fbb09/bootstrap3_datetime/widgets.py#L33 | ||||
|     format_map = ( | ||||
|         ('DDD', r'%j'), | ||||
|         ('DD', r'%d'), | ||||
|         ('MMMM', r'%B'), | ||||
|         ('MMM', r'%b'), | ||||
|         ('MM', r'%m'), | ||||
|         ('YYYY', r'%Y'), | ||||
|         ('YY', r'%y'), | ||||
|         ('HH', r'%H'), | ||||
|         ('hh', r'%I'), | ||||
|         ('mm', r'%M'), | ||||
|         ('ss', r'%S'), | ||||
|         ('a', r'%p'), | ||||
|         ('ZZ', r'%z'), | ||||
|     ) | ||||
|  | ||||
|     class Media: | ||||
|         """JS/CSS resources needed to render the date-picker calendar.""" | ||||
|  | ||||
|         js = ( | ||||
|             'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.9.0/' | ||||
|             'moment-with-locales.min.js', | ||||
|             'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/' | ||||
|             '4.17.47/js/bootstrap-datetimepicker.min.js', | ||||
|             'bootstrap_datepicker_plus/js/datepicker-widget.js' | ||||
|         ) | ||||
|         css = {'all': ( | ||||
|             'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/' | ||||
|             '4.17.47/css/bootstrap-datetimepicker.css', | ||||
|             'bootstrap_datepicker_plus/css/datepicker-widget.css' | ||||
|         ), } | ||||
|  | ||||
|     @classmethod | ||||
|     def format_py2js(cls, datetime_format): | ||||
|         """Convert python datetime format to moment datetime format.""" | ||||
|         for js_format, py_format in cls.format_map: | ||||
|             datetime_format = datetime_format.replace(py_format, js_format) | ||||
|         return datetime_format | ||||
|  | ||||
|     @classmethod | ||||
|     def format_js2py(cls, datetime_format): | ||||
|         """Convert moment datetime format to python datetime format.""" | ||||
|         for js_format, py_format in cls.format_map: | ||||
|             datetime_format = datetime_format.replace(js_format, py_format) | ||||
|         return datetime_format | ||||
|  | ||||
|     def __init__(self, attrs=None, format=None, options=None): | ||||
|         """Initialize the Date-picker widget.""" | ||||
|         self.format_param = format | ||||
|         self.options_param = options if options else {} | ||||
|         self.config = self._default_config.copy() | ||||
|         self.config['id'] = DatePickerDictionary.generate_id() | ||||
|         self.config['picker_type'] = self.picker_type | ||||
|         self.config['options'] = self._calculate_options() | ||||
|         attrs = attrs if attrs else {} | ||||
|         if 'class' not in attrs: | ||||
|             attrs['class'] = 'form-control' | ||||
|         super().__init__(attrs, self._calculate_format()) | ||||
|  | ||||
|     def _calculate_options(self): | ||||
|         """Calculate and Return the options.""" | ||||
|         _options = self._default_options.copy() | ||||
|         _options.update(self.options) | ||||
|         if self.options_param: | ||||
|             _options.update(self.options_param) | ||||
|         return _options | ||||
|  | ||||
|     def _calculate_format(self): | ||||
|         """Calculate and Return the datetime format.""" | ||||
|         _format = self.format_param if self.format_param else self.format | ||||
|         if self.config['options'].get('format'): | ||||
|             _format = self.format_js2py(self.config['options'].get('format')) | ||||
|         else: | ||||
|             self.config['options']['format'] = self.format_py2js(_format) | ||||
|         return _format | ||||
|  | ||||
|     def get_context(self, name, value, attrs): | ||||
|         """Return widget context dictionary.""" | ||||
|         context = super().get_context( | ||||
|             name, value, attrs) | ||||
|         context['widget']['attrs']['dp_config'] = json_dumps(self.config) | ||||
|         return context | ||||
|  | ||||
|     def start_of(self, event_id): | ||||
|         """ | ||||
|         Set Date-Picker as the start-date of a date-range. | ||||
|  | ||||
|         Args: | ||||
|             - event_id (string): User-defined unique id for linking two fields | ||||
|         """ | ||||
|         DatePickerDictionary.items[str(event_id)] = self | ||||
|         return self | ||||
|  | ||||
|     def end_of(self, event_id, import_options=True): | ||||
|         """ | ||||
|         Set Date-Picker as the end-date of a date-range. | ||||
|  | ||||
|         Args: | ||||
|             - event_id (string): User-defined unique id for linking two fields | ||||
|             - import_options (bool): inherit options from start-date input, | ||||
|               default: TRUE | ||||
|         """ | ||||
|         event_id = str(event_id) | ||||
|         if event_id in DatePickerDictionary.items: | ||||
|             linked_picker = DatePickerDictionary.items[event_id] | ||||
|             self.config['linked_to'] = linked_picker.config['id'] | ||||
|             if import_options: | ||||
|                 backup_moment_format = self.config['options']['format'] | ||||
|                 self.config['options'].update(linked_picker.config['options']) | ||||
|                 self.config['options'].update(self.options_param) | ||||
|                 if self.format_param or 'format' in self.options_param: | ||||
|                     self.config['options']['format'] = backup_moment_format | ||||
|                 else: | ||||
|                     self.format = linked_picker.format | ||||
|             # Setting useCurrent is necessary, see following issue | ||||
|             # https://github.com/Eonasdan/bootstrap-datetimepicker/issues/1075 | ||||
|             self.config['options']['useCurrent'] = False | ||||
|             self._link_to(linked_picker) | ||||
|         else: | ||||
|             raise KeyError( | ||||
|                 'start-date not specified for event_id "%s"' % event_id) | ||||
|         return self | ||||
|  | ||||
|     def _link_to(self, linked_picker): | ||||
|         """ | ||||
|         Executed when two date-inputs are linked together. | ||||
|  | ||||
|         This method for sub-classes to override to customize the linking. | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|  | ||||
| class DatePickerInput(BasePickerInput): | ||||
|     """ | ||||
|     Widget to display a Date-Picker Calendar on a DateField property. | ||||
|  | ||||
|     Args: | ||||
|         - attrs (dict): HTML attributes of rendered HTML input | ||||
|         - format (string): Python DateTime format eg. "%Y-%m-%d" | ||||
|         - options (dict): Options to customize the widget, see README | ||||
|     """ | ||||
|  | ||||
|     picker_type = 'DATE' | ||||
|     format = '%Y-%m-%d' | ||||
|     format_key = 'DATE_INPUT_FORMATS' | ||||
|  | ||||
|  | ||||
| class TimePickerInput(BasePickerInput): | ||||
|     """ | ||||
|     Widget to display a Time-Picker Calendar on a TimeField property. | ||||
|  | ||||
|     Args: | ||||
|         - attrs (dict): HTML attributes of rendered HTML input | ||||
|         - format (string): Python DateTime format eg. "%Y-%m-%d" | ||||
|         - options (dict): Options to customize the widget, see README | ||||
|     """ | ||||
|  | ||||
|     picker_type = 'TIME' | ||||
|     format = '%H:%M' | ||||
|     format_key = 'TIME_INPUT_FORMATS' | ||||
|     template_name = 'bootstrap_datepicker_plus/time_picker.html' | ||||
|  | ||||
|  | ||||
| class DateTimePickerInput(BasePickerInput): | ||||
|     """ | ||||
|     Widget to display a DateTime-Picker Calendar on a DateTimeField property. | ||||
|  | ||||
|     Args: | ||||
|         - attrs (dict): HTML attributes of rendered HTML input | ||||
|         - format (string): Python DateTime format eg. "%Y-%m-%d" | ||||
|         - options (dict): Options to customize the widget, see README | ||||
|     """ | ||||
|  | ||||
|     picker_type = 'DATETIME' | ||||
|     format = '%Y-%m-%d %H:%M' | ||||
|     format_key = 'DATETIME_INPUT_FORMATS' | ||||
|  | ||||
|  | ||||
| class MonthPickerInput(BasePickerInput): | ||||
|     """ | ||||
|     Widget to display a Month-Picker Calendar on a DateField property. | ||||
|  | ||||
|     Args: | ||||
|         - attrs (dict): HTML attributes of rendered HTML input | ||||
|         - format (string): Python DateTime format eg. "%Y-%m-%d" | ||||
|         - options (dict): Options to customize the widget, see README | ||||
|     """ | ||||
|  | ||||
|     picker_type = 'MONTH' | ||||
|     format = '01/%m/%Y' | ||||
|     format_key = 'DATE_INPUT_FORMATS' | ||||
|  | ||||
|  | ||||
| class YearPickerInput(BasePickerInput): | ||||
|     """ | ||||
|     Widget to display a Year-Picker Calendar on a DateField property. | ||||
|  | ||||
|     Args: | ||||
|         - attrs (dict): HTML attributes of rendered HTML input | ||||
|         - format (string): Python DateTime format eg. "%Y-%m-%d" | ||||
|         - options (dict): Options to customize the widget, see README | ||||
|     """ | ||||
|  | ||||
|     picker_type = 'YEAR' | ||||
|     format = '01/01/%Y' | ||||
|     format_key = 'DATE_INPUT_FORMATS' | ||||
|  | ||||
|     def _link_to(self, linked_picker): | ||||
|         """Customize the options when linked with other date-time input""" | ||||
|         yformat = self.config['options']['format'].replace('-01-01', '-12-31') | ||||
|         self.config['options']['format'] = yformat | ||||
| @@ -48,6 +48,7 @@ INSTALLED_APPS = [ | ||||
|     'django.contrib.sites', | ||||
|     'django.contrib.messages', | ||||
|     'django.contrib.staticfiles', | ||||
|     'django.forms', | ||||
|     # API | ||||
|     'rest_framework', | ||||
|     'rest_framework.authtoken', | ||||
| @@ -100,6 +101,8 @@ TEMPLATES = [ | ||||
|     }, | ||||
| ] | ||||
|  | ||||
| FORM_RENDERER = 'django.forms.renderers.TemplatesSetting' | ||||
|  | ||||
| WSGI_APPLICATION = 'note_kfet.wsgi.application' | ||||
|  | ||||
| # Password validation | ||||
|   | ||||
							
								
								
									
										121
									
								
								static/bootstrap_datepicker_plus/css/datepicker-widget.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								static/bootstrap_datepicker_plus/css/datepicker-widget.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | ||||
| @font-face { | ||||
|     font-family: 'Glyphicons Halflings'; | ||||
|     src: url('//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.eot'); | ||||
|     src: url('//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), | ||||
|      url('//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.woff2') format('woff2'), | ||||
|      url('//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.woff') format('woff'), | ||||
|      url('//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.ttf') format('truetype'), | ||||
|      url('//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); | ||||
| } | ||||
|  | ||||
| .glyphicon { | ||||
|     position: relative; | ||||
|     top: 1px; | ||||
|     display: inline-block; | ||||
|     font-family: 'Glyphicons Halflings'; | ||||
|     font-style: normal; | ||||
|     font-weight: normal; | ||||
|     line-height: 1; | ||||
|     -webkit-font-smoothing: antialiased; | ||||
|     -moz-osx-font-smoothing: grayscale; | ||||
| } | ||||
|  | ||||
| .glyphicon-time:before { | ||||
|     content: "\e023"; | ||||
| } | ||||
|  | ||||
| .glyphicon-chevron-left:before { | ||||
|     content: "\e079"; | ||||
| } | ||||
|  | ||||
| .glyphicon-chevron-right:before { | ||||
|     content: "\e080"; | ||||
| } | ||||
|  | ||||
| .glyphicon-chevron-up:before { | ||||
|     content: "\e113"; | ||||
| } | ||||
|  | ||||
| .glyphicon-chevron-down:before { | ||||
|     content: "\e114"; | ||||
| } | ||||
|  | ||||
| .glyphicon-calendar:before { | ||||
|     content: "\e109"; | ||||
| } | ||||
|  | ||||
| .glyphicon-screenshot:before { | ||||
|     content: "\e087"; | ||||
| } | ||||
|  | ||||
| .glyphicon-trash:before { | ||||
|     content: "\e020"; | ||||
| } | ||||
|  | ||||
| .glyphicon-remove:before { | ||||
|     content: "\e014"; | ||||
| } | ||||
|  | ||||
| .bootstrap-datetimepicker-widget .btn { | ||||
|     display: inline-block; | ||||
|     padding: 6px 12px; | ||||
|     margin-bottom: 0; | ||||
|     font-size: 14px; | ||||
|     font-weight: normal; | ||||
|     line-height: 1.42857143; | ||||
|     text-align: center; | ||||
|     white-space: nowrap; | ||||
|     vertical-align: middle; | ||||
|     -ms-touch-action: manipulation; | ||||
|     touch-action: manipulation; | ||||
|     cursor: pointer; | ||||
|     -webkit-user-select: none; | ||||
|     -moz-user-select: none; | ||||
|     -ms-user-select: none; | ||||
|     user-select: none; | ||||
|     background-image: none; | ||||
|     border: 1px solid transparent; | ||||
|     border-radius: 4px; | ||||
| } | ||||
|  | ||||
| .bootstrap-datetimepicker-widget.dropdown-menu { | ||||
|     position: absolute; | ||||
|     left: 0; | ||||
|     z-index: 1000; | ||||
|     display: none; | ||||
|     float: left; | ||||
|     min-width: 160px; | ||||
|     padding: 5px 0; | ||||
|     margin: 2px 0 0; | ||||
|     font-size: 14px; | ||||
|     text-align: left; | ||||
|     list-style: none; | ||||
|     background-color: #fff; | ||||
|     -webkit-background-clip: padding-box; | ||||
|     background-clip: padding-box; | ||||
|     border: 1px solid #ccc; | ||||
|     border: 1px solid rgba(0, 0, 0, .15); | ||||
|     border-radius: 4px; | ||||
|     -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); | ||||
|     box-shadow: 0 6px 12px rgba(0, 0, 0, .175); | ||||
| } | ||||
|  | ||||
| .bootstrap-datetimepicker-widget .list-unstyled { | ||||
|     padding-left: 0; | ||||
|     list-style: none; | ||||
| } | ||||
|  | ||||
| .bootstrap-datetimepicker-widget .collapse { | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| .bootstrap-datetimepicker-widget .collapse.in { | ||||
|     display: block; | ||||
| } | ||||
|  | ||||
| /* fix for bootstrap4 */ | ||||
| .bootstrap-datetimepicker-widget .table-condensed > thead > tr > th, | ||||
| .bootstrap-datetimepicker-widget .table-condensed > tbody > tr > td, | ||||
| .bootstrap-datetimepicker-widget .table-condensed > tfoot > tr > td { | ||||
|     padding: 5px; | ||||
| } | ||||
							
								
								
									
										55
									
								
								static/bootstrap_datepicker_plus/js/datepicker-widget.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								static/bootstrap_datepicker_plus/js/datepicker-widget.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| jQuery(function ($) { | ||||
|     var datepickerDict = {}; | ||||
|     var isBootstrap4 = $.fn.collapse.Constructor.VERSION.split('.').shift() == "4"; | ||||
|     function fixMonthEndDate(e, picker) { | ||||
|         e.date && picker.val().length && picker.val(e.date.endOf('month').format('YYYY-MM-DD')); | ||||
|     } | ||||
|     $("[dp_config]:not([disabled])").each(function (i, element) { | ||||
|         var $element = $(element), data = {}; | ||||
|         try { | ||||
|             data = JSON.parse($element.attr('dp_config')); | ||||
|         } | ||||
|         catch (x) { } | ||||
|         if (data.id && data.options) { | ||||
|             data.$element = $element.datetimepicker(data.options); | ||||
|             data.datepickerdata = $element.data("DateTimePicker"); | ||||
|             datepickerDict[data.id] = data; | ||||
|             data.$element.next('.input-group-addon').on('click', function(){ | ||||
|                 data.datepickerdata.show(); | ||||
|             }); | ||||
|             if(isBootstrap4){ | ||||
|                 data.$element.on("dp.show", function (e) { | ||||
|                     $('.collapse.in').addClass('show'); | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|     $.each(datepickerDict, function (id, to_picker) { | ||||
|         if (to_picker.linked_to) { | ||||
|             var from_picker = datepickerDict[to_picker.linked_to]; | ||||
|             from_picker.datepickerdata.maxDate(to_picker.datepickerdata.date() || false); | ||||
|             to_picker.datepickerdata.minDate(from_picker.datepickerdata.date() || false); | ||||
|             from_picker.$element.on("dp.change", function (e) { | ||||
|                 to_picker.datepickerdata.minDate(e.date || false); | ||||
|             }); | ||||
|             to_picker.$element.on("dp.change", function (e) { | ||||
|                 if (to_picker.picker_type == 'MONTH') fixMonthEndDate(e, to_picker.$element); | ||||
|                 from_picker.datepickerdata.maxDate(e.date || false); | ||||
|             }); | ||||
|             if (to_picker.picker_type == 'MONTH') { | ||||
|                 to_picker.$element.on("dp.hide", function (e) { | ||||
|                     fixMonthEndDate(e, to_picker.$element); | ||||
|                 }); | ||||
|                 fixMonthEndDate({ date: to_picker.datepickerdata.date() }, to_picker.$element); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|     if(isBootstrap4) { | ||||
|         $('body').on('show.bs.collapse','.bootstrap-datetimepicker-widget .collapse',function(e){ | ||||
|             $(e.target).addClass('in'); | ||||
|         }); | ||||
|         $('body').on('hidden.bs.collapse','.bootstrap-datetimepicker-widget .collapse',function(e){ | ||||
|             $(e.target).removeClass('in'); | ||||
|         }); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										6
									
								
								templates/bootstrap_datepicker_plus/date_picker.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								templates/bootstrap_datepicker_plus/date_picker.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <div class="input-group date"> | ||||
|     {% include "bootstrap_datepicker_plus/input.html" %} | ||||
|     <div class="input-group-addon input-group-append" data-target="#datetimepicker1" data-toggle="datetimepickerv"> | ||||
|         <div class="input-group-text"><i class="glyphicon glyphicon-calendar"></i></div> | ||||
|     </div> | ||||
| </div> | ||||
							
								
								
									
										4
									
								
								templates/bootstrap_datepicker_plus/input.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								templates/bootstrap_datepicker_plus/input.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| <input type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None and widget.value != "" %} | ||||
|        value="{{ widget.value }}"{% endif %}{% for name, value in widget.attrs.items %}{% ifnotequal value False %} | ||||
|     {{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %} | ||||
| {% endifnotequal %}{% endfor %}/> | ||||
							
								
								
									
										6
									
								
								templates/bootstrap_datepicker_plus/time_picker.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								templates/bootstrap_datepicker_plus/time_picker.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <div class="input-group date"> | ||||
|     {% include "bootstrap_datepicker_plus/input.html" %} | ||||
|     <div class="input-group-addon input-group-append" data-target="#datetimepicker1" data-toggle="datetimepickerv"> | ||||
|         <div class="input-group-text"><i class="glyphicon glyphicon-time"></i></div> | ||||
|     </div> | ||||
| </div> | ||||
							
								
								
									
										11
									
								
								templates/note/amount_input.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								templates/note/amount_input.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <div class="input-group"> | ||||
|     <input class="form-control mx-auto d-block" type="number" min="0" step="0.01" | ||||
|            {% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %} | ||||
|            name="{{ widget.name }}" | ||||
|             {% for name, value in widget.attrs.items %} | ||||
|                 {% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %} | ||||
|             {% endfor %}> | ||||
|     <div class="input-group-append"> | ||||
|         <span class="input-group-text">€</span> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -126,12 +126,7 @@ SPDX-License-Identifier: GPL-2.0-or-later | ||||
|     <div class="form-row"> | ||||
|         <div class="form-group col-md-6"> | ||||
|             <label for="amount">{% trans "Amount" %} :</label> | ||||
|             <div class="input-group"> | ||||
|                 <input class="form-control mx-auto d-block" type="number" min="0" step="0.01" id="amount" /> | ||||
|                 <div class="input-group-append"> | ||||
|                     <span class="input-group-text">€</span> | ||||
|                 </div> | ||||
|             </div> | ||||
|             {% include "note/amount_input.html" with widget=amount_widget %} | ||||
|         </div> | ||||
|  | ||||
|         <div class="form-group col-md-6"> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| {% extends "base.html" %} | ||||
| {% load static %} | ||||
| {% load i18n %} | ||||
| {% load crispy_forms_tags pretty_money %} | ||||
| {% load crispy_forms_tags %} | ||||
| {% block content %} | ||||
|     <p><a class="btn btn-default" href="{% url 'treasury:invoice_list' %}">{% trans "Invoices list" %}</a></p> | ||||
|     <form method="post" action=""> | ||||
| @@ -27,17 +27,7 @@ | ||||
|                 <tr class="row-formset"> | ||||
|                     <td>{{ form.designation }}</td> | ||||
|                     <td>{{ form.quantity }}</td> | ||||
|                     <td> | ||||
|                         {# Use custom input for amount, with the € symbol #} | ||||
|                         <div class="input-group"> | ||||
|                             <input type="number" name="product_set-{{ forloop.counter0 }}-amount" step="0.01" | ||||
|                                    id="id_product_set-{{ forloop.counter0 }}-amount" | ||||
|                                    value="{{ form.instance.amount|cents_to_euros }}"> | ||||
|                             <div class="input-group-append"> | ||||
|                                 <span class="input-group-text">€</span> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </td> | ||||
|                     <td>{{ form.amount }}</td> | ||||
|                     {# These fields are hidden but handled by the formset to link the id and the invoice id #} | ||||
|                     {{ form.invoice }} | ||||
|                     {{ form.id }} | ||||
| @@ -64,15 +54,7 @@ | ||||
|             <tr class="row-formset"> | ||||
|                 <td>{{ formset.empty_form.designation }}</td> | ||||
|                 <td>{{ formset.empty_form.quantity }} </td> | ||||
|                 <td> | ||||
|                     <div class="input-group"> | ||||
|                         <input type="number" name="product_set-__prefix__-amount" step="0.01" | ||||
|                                id="id_product_set-__prefix__-amount"> | ||||
|                         <div class="input-group-append"> | ||||
|                             <span class="input-group-text">€</span> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </td> | ||||
|                 <td>{{ formset.empty_form.amount }}</td> | ||||
|                 {{ formset.empty_form.invoice }} | ||||
|                 {{ formset.empty_form.id }} | ||||
|             </tr> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user