How to create and save a model with multiselect values in Django - django

I am trying to create a model where I can assign multiple days of the week to a Customer through a form. However, when I try to save it I get the following error.
django.db.utils.DataError: value too long for type character varying(2)
I am using the django-multiselectfield package to create multiple select checkboxes.
https://pypi.org/project/django-multiselectfield/
model.py
from multiselectfield import MultiSelectField
DAYS_OF_THE_WEEK_CHOICES = [
('Mo', 'Monday'),
('Tu', 'Tuesday'),
('We', 'Wednesday'),
('Th', 'Thursday'),
('Fr', 'Friday'),
('Sa', 'Saturday'),
('Su', 'Sunday'),
]
class Customer(models.Model):
days_of_the_week = MultiSelectField(choices=DAYS_OF_THE_WEEK_CHOICES)
forms.py
RECURRINGDAYS = [
('Mo','Monday'),
('Tu','Tuesday'),
('We','Wednesday'),
('Th','Thursday'),
('Fr','Friday'),
('Sa','Saturday'),
('Su','Sunday')
]
class CustomerCreateForm(forms.Form):
days_of_the_week = forms.MultipleChoiceField(required=False, choices=RECURRINGDAYS, widget=forms.CheckboxSelectMultiple(), label="Recurring Day(s)?")
views.py
from .models import Customer
from .forms import CustomerCreateForm
def create_customer(request):
form = CustomerCreateForm(request.POST or None)
if request.POST:
if form.is_valid():
recurringDays = form.cleaned_data['days_of_the_week']
newCustomer = Customer(
days_of_the_week = recurringDays
)
newCustomer.save()
return render(request, "customer.html", context)
If someone fills out the form and selects Monday and Tuesday, how do I save that to the database Customer model?

This code works, I just spent a few hours to discover I forgot to makemigrations.
Whoops.

Related

How to remove an already selected option from options list to avoid double bookings

I've been scratching my head with this for 2 days hoping for a sudden brainwave and getting nowhere. I've completely drawn a blank with my logic and all attempts have resulted in me breaking my code. I'm trying to get it so that on a specific date, if a user has already selected a time slot with a selected barber, that that time slot will be removed from the list of time slots, so it cannot be selected again by another user.
From models.py
from django.db import models
import datetime
from django.core.validators import MinValueValidator
from django.contrib.auth.models import User
SERVICES = (
('Gents Cut', 'Gents Cut'),
('Kids Cut', 'Kids Cut'),
('Cut and Shave', 'Cut and Shave'),
('Shave Only', 'Shave Only'),
)
TIME_SLOTS = (
('9.00 - 10.00', '9.00 - 10.00'),
('10.00 - 11.00', '10.00 - 11.00'),
('11.00 - 12.00', '11.00 - 12.00'),
('12.00 - 13.00', '12.00 - 13.00'),
('13.00 - 14.00', '13.00 - 14.00'),
('14.00 - 15.00', '14.00 - 15.00'),
('15.00 - 16.00', '15.00 - 16.00'),
('16.00 - 17.00', '16.00 - 17.00'),
('17.00 - 18.00', '17.00 - 18.00'),
)
BARBER_NAME = (
('Nathan', 'Nathan'),
('Chris', 'Chris'),
('Ben', 'Ben'),
('Dan', 'Dan'),
)
class Booking(models.Model):
date = models.DateField(validators=[MinValueValidator(datetime.date.today)])
time = models.CharField(max_length=50, null=True, choices=TIME_SLOTS)
barber = models.CharField(max_length=50, null=True, choices=BARBER_NAME)
service = models.CharField(max_length=50, null=True, choices=SERVICES)
customer = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return f"{self.customer} has booked {self.service} on {self.date} at {self.time} with {self.barber}"
from views.py
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.views import generic, View
from django.contrib import messages
from django.http import HttpResponseRedirect
from .models import Booking
from .forms import BookingForm
#login_required()
def make_booking(request):
if request.method == "POST":
form = BookingForm(request.POST)
if form.is_valid():
booking_form = form.save(commit=False)
booking_form.customer = request.user
booking_form.save()
messages.success(request, ('Your booking is awaiting confirmation'))
return HttpResponseRedirect('/bookings')
else:
form = BookingForm()
return render(request, 'bookings.html', {'form': form})
from forms.py
from .models import Booking
import datetime
from django import forms
class DatePicker(forms.DateInput):
input_type = 'date'
class BookingForm(forms.ModelForm):
class Meta:
model = Booking
fields = ('date', 'time', 'barber', 'service',)
widgets = {
'date': DatePicker(),
}
One way or another you will need another view to accept the selected date and return the available time slots. If you were to do this via AJAX then your view might look something like this:
import json
from datetime import datetime
def get_time_slots(request):
# get the date in python format, sent via ajax request
date = datetime.strptime(request.GET.get('date'), '%Y-%m-%d')
# get all times from bookings on the provided date
booked_slots = Booking.objects.filter(date=date).values_list('time', flat=True)
available_slots = []
for slots in TIME_SLOTS:
if slots[0] not in booked_slots:
available_slots.append(slots)
times_as_json = json.dumps(available_slots)
return JsonResponse(times_as_json)
A better way, imo, would be to create new models for time slots, services, and barbers. Firstly, you are not hardcoding information and can dynamically change things in the DB. Secondly, you can generate a very slick query to get the available time slots for a day, barber, service, etc. You can also set general availability by linking certain barbers to certain time slots, depending on the hours they work.

Django modelform NameError: name 'check_in' is not defined

Hi everyone I am a beginner in django, building a lodge reservation system with django3.0 and i can't seem to get my modelform to generate a proper queryset to get data in my Reservations model, i keep getting the NameError error message everytime i try enter a new date through my view and right now im not quite sure how to properly solve this error
here is my models.py:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
now = timezone.now
end = timezone.now() + timezone.timedelta(days=2)
room_choices = [
('single_room', 'Single Room'),
('double_room', 'Double Room'),
('executive_room', 'Executive Room'),
]
class Reservation(models.Model):
room_type = models.CharField(max_length=30, choices=room_choices, default=room_choices[1])
check_in = models.DateField(default=timezone.now)
check_out = models.DateField(default=end)
class Meta:
verbose_name = 'Reservation'
verbose_name_plural = 'Reservations'
I would like to add validation statements later in views.py for price and number of rooms depending on the room_type the user selected so disregard the field for now
here is my forms.py:
from django import forms
from .models import Reservation
class AvailabilityForm(forms.ModelForm):
class Meta:
model = Reservation
fields = [
"room_type",
"check_in",
"check_out",
]
widgets = {
'check_in': forms.DateInput(format='%m/%d/%Y'),
'check_out': forms.DateInput(format='%m/%d/%Y'),
}
If someone knows how to get the widgets to work properly outside of admin, displaying the calender onclick and not not just a charfield with a datefield inside, please also help me fix this problem as well
here is my views.py:
from django.shortcuts import get_object_or_404, render
from datetime import timedelta, date
from django.contrib.auth.models import User
from django.contrib import messages
from . import forms
from .models import Reservation
def availability(request):
form = forms.AvailabilityForm()
reservation = Reservation
if request.method == 'POST':
form = forms.AvailabilityForm(request.POST or None)
if form.is_valid:
reserve = form.save(commit=False)
reserve.reservation = reservation
# check whether the dates are valid
# case 1: a room is booked before the check_in date, and checks out after the requested check_in date
case_1 = Reservation.objects.filter(check_in__lte=reserve.check_in).filter(check_out__gte=check_in)
# case 2: oom is booked before the requested check_out date and check_out date is after requested check_out date
case_2 = Reservation.objects.filter(check_in__lte=check_out, check_out__gte=check_out).exists()
#case3: room is booked in a date which lies between the two requested check-in/check-out dates
case_3 = Reservation.objects.filter(check_in__gte=check_in, check_out__lte=check_out).exists()
# if either of these is true, abort and render the error
if case_1 or case_2 or case_3:
return render(request, "availability.html", {"errors": "This room is not available on your selected dates", "form": form})
# else dates are valid
reserve.save()
messages.add_message(request, messages.SUCCESS, 'Room is available for your stay here')
return redirect("/complete_booking")
return render(request, "availability.html", {"form": form})
i have tried different methods to try get my view to validate the check_in and check_out objects as you can see case1 and case2 use different filtering techniques but neither seem to work, any help would be appreciated as to where i'm going wrong and how to fix it
From the comments, the error is:
File "C:\Users\Karabo\Documents\technocrats2\core\views.py", line 160, in availability
case_1 = Reservation.objects.filter(check_in__lte=check_in).filter(check_out__gte=check_out).exists()
NameError: name 'check_in' is not defined
After checking the traceback, it is clear that you haven't define any check_in variable. You need to define the variable before using it.
check_in = "18-01-2020" # value you want to use in queryset.
case_1 = Reservation.objects.filter(check_in__lte=reserve.check_in).filter(check_out__gte=check_in)

Creating buttons in a column of a table created by django tables2

I have an extended admin model that creates action buttons. I have created a view to do pretty much the same thing. I have used tables2 and everything is just fine except for the actions column. I cannot find a way to generate the same button in the table. Is there a way to do this at all?
tables.py
from .models import Ticket
import django_tables2 as tables
'''from .admin import river_actions, create_river_button'''
class TicketTable(tables.Table):
class Meta:
model=Ticket
template_name='django_tables2/table.html'
fields = ('id','subject','request_type','material_type','productline','business','measurement_system',
'created_at','updated_at','status','river_action','project') # fields to display
attrs = {'class': 'mytable'}
'''attrs = {"class": "table-striped table-bordered"}'''
empty_text = "There are no tickets matching the search criteria..."
admin.py (the part that includes the model etc)
# Define a new User admin to get client info too while defining users
class UserAdmin(BaseUserAdmin):
inlines = (UserExtendInline, )
def create_river_button(obj,proceeding):
return '''
<input
type="button"
style=margin:2px;2px;2px;2px;"
value="%s"
onclick="location.href=\'%s\'"
/>
'''%(proceeding.meta.transition,
reverse('proceed_ticket',kwargs={'ticket_id':obj.pk, 'next_state_id':proceeding.meta.transition.destination_state.pk})
)
class TicketAdmin(admin.ModelAdmin):
list_display=('id','client','subject','request_type','material_type','productline','business','measurement_system', \
'created_at','updated_at','created_by','status','river_actions')
#list_display_links=None if has_model_permissions(request.user,Ticket,['view_ticket'],'mmrapp')==True else list_display
#list_display_links=list_display #use None to remove all links, or use a list to make some fields clickable
#search_fields = ('subject','material_type')
list_filter=[item for item in list_display if item!='river_actions'] #exclude river_actions since it is not related to a field and cannot be filtered
#Using fieldset, we can control which fields should be filled by the user in the ADD method. This way, created_by will be the
#logged in user and not a drop down choice on the admin site
fieldsets = [
(None, {
'fields': ('client','subject','description','request_type','material_type', \
'productline','business','measurement_system', 'project')
} ), #to make some field appear horizontal, put them into a []
]
formfield_overrides = {
models.CharField: {'widget': TextInput (attrs={'size':'40'})},
models.TextField: {'widget': Textarea(attrs={'rows':4, 'cols':80})},}
def get_list_display(self, request):
self.user=request.user
return super(TicketAdmin,self).get_list_display(request)
def river_actions(self,obj):
content=""
for proceeding in obj.get_available_proceedings(self.user):
content+=create_river_button(obj, proceeding)
return content
river_actions.allow_tags=True
#override save_model method to save current user since it is not on the admin page form anymore
def save_model(self, request, obj, form, change):
if not change:
# the object is being created and not changed, so set the user
obj.created_by = request.user
obj.save()
views.py
def display_tickets(request):
table = TicketTable(Ticket.objects.all())
RequestConfig(request).configure(table)
'''table = CustomerTable(Customer.objects.filter(self.kwargs['company']).order_by('-pk'))'''
return render(request,'mmrapp/ticket_display.html',{'table':table})
buttons created in admin page:
table created using tables2 in views missing buttons:
You must pass empty_values=() to the column, because by default, django-tables2 only renders the column if the value is not contained in empty_values for that column.
import django_tables2 as tables
from .admin import river_actions, create_river_button
from .models import Ticket
class TicketTable(tables.Table):
river_action = tables.Column(empty_values=())
class Meta:
model=Ticket
template_name='django_tables2/table.html'
fields = (
'id', 'subject', 'request_type', 'material_type', 'productline', 'business', 'measurement_system',
'created_at', 'updated_at', 'status', 'river_action', 'project'
) # fields to display
attrs = {'class': 'mytable'}
empty_text = "There are no tickets matching the search criteria..."
def render_river_action(self, record):
return create_river_button(record, ...)
This is also documented as Table.render_foo methods

Date Conveniences (Validation, Display, etc) for Partial Dates in Django

I am trying to store dates in my database that often lack the month and day. My current approach (there seem to be many different ways of doing this) is to use three fields for this:
dob_year
dob_month
dob_day
I'd like to get as many of the benefits of DateFields that I can. The most important one I can think of now is validation. Valid:
2010
2010,02
2010,02,28
Invalid:
2010,02,30
There's also the problem of converting ints into a human readable form in the templates. It's great to be able to say:
<p>{{my_date|date:"%Y"}}</p>
But with this, I'll have to do something very strange because I'll need it to support partial dates and regular ones. I'm thinking I might be able to accomplish this with a #property method, but I haven't sorted this out yet either.
I'm sure there are other conveniences I'm giving up too (like date widgets in admin). Ideas here are welcome too since I know partial dates are a common issue, but I'm mostly concerned with validation at the moment.
Update: A friend on Twitter pointed out that using three fields for this creates horrible date queries. Do not use three fields for partial dates unless you want to think about how to handle queries like "between July of 2011 and June of 2012".
Store dates in database as text is a bad practice. It is not portable, not django query api friendly, and not index friendly.
You can store all date in database even not all date is needed. For example, Oracle stores date and time in dates even you only need the date.
Then you can use a DateField + "ChoiceField" to store partialdate and relevant part information. Ex: 2015-02-01 truncate at month level. 2015-02-28 Full date. 2015-01-01 truncate at year level. Code:
class Item(models.Model):
PARTIAL_YEAR='%Y'
PARTIAL_MONTH='%Y-%m'
PARTIAL_DAY='%Y-%m-%d'
PARTIAL_CHOICES = (
(PARTIAL_YEAR, 'Year'),
(PARTIAL_MONTH, 'Month'),
(PARTIAL_DAY, 'Day'),
)
partial_date_date = models.DateField()
partial_date_part = models.CharField('Date part',
choices=PARTIAL_CHOICES,
max_length=10, )
Validate Both fields will be validated by each own widget. You can add a form clean (Cleaning and validating fields that depend on each other) or model clean validation level.
Quering Easy to make conditioned queries using Q objects:
q_is_year = q( partial_date_part = Item.PARTIAL_YEAR )
q_by_year = q( partial_date_date__year = 2015 )
q_is_month = q( partial_date_part = Item.PARTIAL_MONTH )
q_by_month = q( partial_date_date__year = 2105 )
q_by_month &= q( partial_date_date__month = 2 )
qs = Item.objects.filter( q_is_year&q_by_year | q_is_month&q_by_month )
Render display In order to render in template:
<p>{{item.partial_date_date|date:item.partial_date_part}}</p>
Render form To render form controls you can use JavaScript to change UI and help user with data entry:
date type: (*) Year ( ) Month ( ) Day
date: [ change form widget dynamically ]
You can send 3 widgets controls to form and show just one at a time. Change visibility on changing radio date type selection. I use MultiWidget.
EDITED 13 August 2015 with all sample code:
models.py
from django.db import models
from datetime import date
class Item(models.Model):
PARTIAL_YEAR='%Y'
PARTIAL_MONTH='%Y-%m'
PARTIAL_DAY='%Y-%m-%d'
PARTIAL_CHOICES = (
(PARTIAL_YEAR, 'Year'),
(PARTIAL_MONTH, 'Month'),
(PARTIAL_DAY, 'Day'),
)
partial_date_part = models.CharField('Date part',
choices=PARTIAL_CHOICES,
max_length=10, )
partial_date_date = models.DateField()
some_comment = models.CharField('Comment', max_length=100, )
def save(self, *args, **kwargs):
if self.partial_date_part==self.PARTIAL_YEAR:
self.partial_date_date = date( self.partial_date_date.year, 1, 1 )
elif self.partial_date_part==self.PARTIAL_MONTH:
self.partial_date_date = date( self.partial_date_date.year,
self.partial_date_date.month, 1 )
super(Item, self).save(*args, **kwargs)
forms.py
from django import forms
from django.forms import widgets
from datetime import date
class DateSelectorWidget(widgets.MultiWidget):
def __init__(self, attrs=None):
days = [(d, d) for d in range(1,32)]
months = [(m, m) for m in range(1,13)]
years = [(year, year) for year in (2011, 2012, 2013)]
_widgets = (
widgets.Select(attrs=attrs, choices=days),
widgets.Select(attrs=attrs, choices=months),
widgets.Select(attrs=attrs, choices=years),
)
super(DateSelectorWidget, self).__init__(_widgets, attrs)
def decompress(self, value):
if value:
return [value.day, value.month, value.year]
return [None, None, None]
def format_output(self, rendered_widgets):
return ''.join(rendered_widgets)
def value_from_datadict(self, data, files, name):
datelist = [
widget.value_from_datadict(data, files, name + '_%s' % i)
for i, widget in enumerate(self.widgets)]
D = date(
day=int(datelist[0]),month=int(datelist[1]),year=int(datelist[2]),
)
return D
class ItemForm(forms.Form):
partial_date_part = forms.CharField(widget=forms.RadioSelect)
partial_date_date = DateSelectorWidget( )
view.py
from django.http import HttpResponseRedirect
from django.views.generic import View
from models import Item
from django.forms.models import modelform_factory
from .forms import DateSelectorWidget
from django import forms
from django.forms import widgets
class MyDatAppView(View):
form_class = modelform_factory(Item ,
exclude=[],
widgets={ 'partial_date_date':
DateSelectorWidget() ,})
initial = {'some_comment': '-*-', }
template_name = 'form.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
m=form.save()
return HttpResponseRedirect('/')
return render(request, self.template_name, {'form': form})
You should add javascript on template to hide/show date part fields when selected part changes.

Radio buttons in django Forms

I'm having difficulty settings up the forms.py file to include a radio or select button. I looked at the documentation but was having no luck applying the correct syntax.
Here is what I currently have in forms.py--
from django import forms
class PictureForm(forms.Form):
like = forms.ChoiceField(???)
name = forms.CharField()
email = forms.EmailField()
message = forms.CharField()
And in my views.py --
from app.forms import PictureForm
def index2(request):
if request.method == 'POST':
form = PictureForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
Picture.objects.create(like=cd['like'], name=cd['name'], email=cd['email'], message=cd['message'])
return HttpResponseRedirect ('/thanks/')
else:
form = PictureForm()
return render_to_response('index2.html', {'form':form},)
How can I set up a set of radio buttons of 'value1', 'value2', 'value3'?
How to do this with a select dropdown?
Thank you.
Look at setting the field's widget and choices when writing the form class.
from django import forms
class PictureForm(forms.Form):
CHOICES = [
('1', 'Option 1'),
('2', 'Option 2'),
]
like = forms.ChoiceField(
widget=forms.RadioSelect,
choices=CHOICES,
)
The default widget of ChoiceField is a drop down select.
The choices argument has the same format as the one of a model field.
Remember to remove all the external styling, and then add the code below:
CHOICES = [('M','Male'),('F','Female')]
Gender=forms.CharField(label='Gender', widget=forms.RadioSelect(choices=CHOICES))
The above code generates Radio buttons.
In my case, I was using a style.css file which was forcing the radio button to render as a list.