Is there any what to change data display in readonly InputField of ModelChoiceField, but retain the primary key of the object for submitting the form?
views.py
class BookingCreateView(LoginRequiredMixin, CreateView):
login_url = 'login'
form_class = BookingForm
template_name = 'booking_add.html'
success_url = reverse_lazy('booking_list')
def get_initial(self):
initial = super(BookingCreateView, self).get_initial()
initial['date'] = datetime.datetime.strptime(self.request.GET.get('date'), '%d-%m-%Y')
initial['room'] = get_object_or_404(Room, id=self.request.GET.get('room'))
initial['start'] = get_object_or_404(Period, number=self.request.GET.get('start'))
return initial
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
forms.py
class BookingForm(forms.ModelForm):
class Meta:
model = Booking
fields= ['room', 'date', 'start', 'end']
def __init__(self, *args, **kwargs):
initial_args = kwargs.get('initial', None)
if initial_args:
super(BookingForm, self).__init__(*args, **kwargs)
self.fields['room'].widget = forms.TextInput()
self.fields['start'].widget = forms.TextInput()
self.fields['room'].widget.attrs['readonly'] = True
self.fields['date'].widget.attrs['readonly'] = True
self.fields['start'].widget.attrs['readonly'] = True
self.fields['end'].queryset = Period.objects.get_available_periods(
initial_args['room'], initial_args['date'], initial_args['start'])
def clean(self):
cleaned_data = super(BookingForm, self).clean()
now = timezone.localtime(timezone.now())
bookings = Booking.objects.filter(room=cleaned_data['room'], date=cleaned_data['date'])
booking_start_time = datetime.datetime.combine(cleaned_data['date'], cleaned_data['start'].start, timezone.get_current_timezone())
booking_end_time = datetime.datetime.combine(cleaned_data['date'], cleaned_data['end'].end, timezone.get_current_timezone())
for booking in bookings:
if booking.check_overlap(booking_start_time, booking_end_time):
raise forms.ValidationError
if now > datetime.datetime.combine(cleaned_data['date'],
cleaned_data['start'].end, timezone.get_current_timezone()):
raise forms.ValidationError
return cleaned_data
booking_add.html
{% block content %}
<main>
<div class="reg-form">
<form class="form" method="post" action="">
{% csrf_token %}
<label for="room">Phòng</label>
{{ form.room }}
<label for="date">Ngày</label>
{{ form.date }}
<label for="start">Ca bắt đầu</label>
{{ form.start }}
<label for="end">Ca kết thúc</label>
{{ form.end }}
<button type="submit">Đăng ký</button>
</form>
</div>
</main>
{% endblock %}
The page is rendered like this:
The thing I want is that the input below label 'Phòng' which mean Room, filled with the room object method str() not the primary key of Room object and the submitting process still send the primary key.Is there any way to achieve that? Note: the first three fields need to be read only and their data are given via GET request.
I know that however I want the field room to be read-only and select widget doesn't have that attribute.
Related
I am working on a project. Need help in template focus out events on Django.
model.py
class Route(models.Model):
route_no = models.SmallIntegerField(default=0)
xname = models.CharField(max_length=40)
class Booth(models.Model):
booth_no = models.SmallIntegerField(default=0)
route_no = models.ForeignKey(Route,
on_delete=models.CASCADE,
db_column='route_no')
View.py
class BoothCreateListView(CreateView, ListView):
model = models.Booth
form_class = booth.BoothForm
template_name = 'booth/booth_create_list.html'
context_object_name = 'booth_list'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
js_data = list(models.Route.objects.all().values_list('route_no', 'xname'))
context['js_data'] = json.dumps(list(js_data), cls=DjangoJSONEncoder)
return context
template/booth_create_list.html
<div class="col-sm-12 col-md-5">
<form method="post">
{% csrf_token %}
<table class="table table-borderless">
{{ form.as_table }}
</table>
<input class="btn btn-success" type="submit" value="Save">
</form>
{{ form.route.value }}
</div>
<div id='route_no'></div>
<script>
var route_no = document.getElementById('route_no')
var myfunction = function (){
console.log('changing');
route.innerHTML = '{{ check_route_no form.route.value }}'
}
</script>
form/booth.py
class BoothForm(ModelForm):
class Meta:
fields = [
'route_no', 'booth_no',
]
model = models.Booth
widgets = {
'route_no': forms.TextInput(),
}
labels = {
'route_no': 'Route No.',
'booth_no': 'Booth No',
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['route_no'].widget.attrs.update(
{'onfocusout':'myfunction()'}
)
for name, field in self.fields.items():
field.widget.attrs.update(
{'class': 'form-control form-control-user'}
)
templatetags/booth_template_tags.py
from booth import models
register = template.Library()
#register.simple_tag
def check_route_no(route_no):
print(route_no)
route = models.Route.objects.filter(route_no=route_no)
if route.count() == 1:
return route.xname
else:
return "not present"
I want to check the route as user types it in the form for the booth. If route_no is present then show the route xname else not present.
My value passed to the template tag is always none. I am not able to pass the textbox value to the template tag to search in DB.
Please help to check runtime if the route no is there in DB as the user type.
I have one app that holds a list of work orders, and another app that holds list of parts.
class Order(models.Model):
parts = models.ManyToManyField(Part, blank=True) # Assosiated parts
class Part(models.Model):
partnum = models.CharField(max_length=20) # Part number
mwos = models.ManyToManyField('mtn.Order', blank=True) # Assosiated work orders
Now i want to add a button to my DetailView for order which will open a list of parts, which i will be able to add to my order. At the moment i have a created an UpdateView for my order
class AddPartView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Order
form_class = AddPartForm
...
and a form
class AddPartForm(forms.ModelForm):
class Meta:
model = Order
fields = ['parts', ]
labels = {'parts': "Parts", }
def FilterList(request):
qs = Part.objects.all()
search_part_query = request.GET.get('search_part')
if is_valid_queryparam(search_part_query):
qs = qs.filter(Q(partnum__icontains=search_part_query)
| Q(descr__icontains=search_part_query)
).distinct()
return qs
def __init__(self, *args, **kwargs):
super(AddPartForm, self).__init__(*args, **kwargs)
self.fields["parts"].widget = CheckboxSelectMultiple()
self.fields["parts"].queryset = self.FilterList()
for this template
{% block content %}
<form method="GET" action=".">
<div class="form-row justify-content-start">
<div class="form-group col-md align-self-center">
<div class="input-group">
<input class="form-conrol py-2 border-right-0 border" type="search" placeholder="Find part" name="search_part">
<span class="input-group-append">
<div class="input-group-text bg-transparent">
<i class="fa fa-search"></i>
</div>
</span>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary btn-sm btn-block">Search</button>
</form>
<form action="{% url 'mtn:add_part' order.id %}" method='post'>
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Save</button>
</form>
{% endblock content %}
But when i'm executing it i get
'AddPartForm' object has no attribute 'GET'
error.
I am new to programming, so maybe i am approaching this the wrong way.
A form is normally not aware of the request object. You can make such a form, for example with:
class AddPartForm(forms.ModelForm):
class Meta:
model = Order
fields = ['parts', ]
labels = {'parts': "Parts", }
widgets = {
'parts': CheckboxSelectMultiple
}
def filter_list(self, request):
qs = Part.objects.all()
search_part_query = request.GET.get('search_part')
if is_valid_queryparam(search_part_query):
qs = qs.filter(Q(partnum__icontains=search_part_query)
| Q(descr__icontains=search_part_query)
).distinct()
return qs
def __init__(self, *args, request=None, **kwargs):
super(AddPartForm, self).__init__(*args, **kwargs)
self.fields["parts"].queryset = self.filter_list(request)
In the AddPartView, you can the pass the request as parameter to the form by overriding the .get_form_kwargs(..) method [Django-doc]
class AddPartView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Order
form_class = AddPartForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update(request=self.request)
return kwargs
I have a modelformset to populate the timetable model.
Models
class Timetable(models.Model):
day = models.ForeignKey('Day',on_delete=models.CASCADE)
start = models.IntegerField()
end = models.IntegerField()
period = models.CharField(max_length=12)
classteacher = models.ForeignKey('Class_teacher',on_delete=models.SET_NULL)
class Class_teacher(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
empid = models.CharField(max_length=10)
email = models.CharField(max_length=30)
Views
class Timetableadding(CreateView):
model = Timetable
success_url = '/dashboard'
form_class = Timetableform
template_name = 'newtest.html'
def get_context_data(self, **kwargs):
context = super(Timetableadding, self).get_context_data(**kwargs)
context['formset'] = TimetableFormSet(queryset=Timetable.objects.none())
return context
def post(self, request, *args, **kwargs):
formset = TimetableFormSet(request.POST)
if formset.is_valid():
return self.form_valid(formset)
def form_valid(self, formset):
formset.classteacher = get_object_or_404(Class_teacher, email=self.request.user.email)
formset.save()
# return super().form_valid(formset)
return HttpResponseRedirect('/dashboard')
Forms
class Timetableform(ModelForm):
class Meta:
model = Timetable
fields = ( 'start', 'end', 'period')
TimetableFormSet = modelformset_factory(Timetable, fields=('start', 'end', 'period'),extra=8,)
Template
<form class="form-material m-t-40 floating-labels" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
{{ form }}<br><br>
{% endfor %}
<div class="form-group row">
<button type="submit" class="btn waves-effect waves-light btn-rounded btn-success">
Submit
</button>
</div>
</form>
While populating the Timetableform using the createview view the fields start end period in Timetable model is done like a general form.
Requirements
The webapp has a login feature . When the user ( classteacher ) login they can add timetable. What I want is classteacher field in Timetable(model Form ) should be automatically set as user which is the classteacher. ( Classteacher ) and should be saved in the db after creating the timetable. Classteacher model is updated with respective required fields .
I tried passing classteacher to formset , but it was execute as I need
I know how to do within a normal form , But I have not done this in a formset.
The day field in the Timetable should be selected only once , so there will be 8 forms to supply start end and period but there should be only one form to supply day. I succeeded in dealing 8 form for start end and period but unaware about the day.
There are better ways to do this:
If you use an inlineformset_factory you can pass the teacher instance to which the form set belongs to directly when initializing the formset. See the example here.
Or you can loop through the forms in order to modify the instances before they are saved:
instances = formset.save(commit=False)
for instance in instances:
instance.classteacher = ...
instance.save()
If you want the user to submit an extra field that is common to all instances, you can add another form to your view:
class DayForm(forms.Form):
day = ModelChoiceField(queryset=Day.objects.all())
def get_context_data(self, **kwargs):
...
context['day_form'] = DayForm()
return context
def post(self, *args, **kwargs):
...
day_form = DayForm(data=request.POST)
if formset.is_valid() and day_form.is_valid():
return self.form_valid(formset, day_form)
def form_valid(self, formset, day_form):
day = day_form.cleaned_data['day']
instances = formset.save(commit=False)
for instance in instances:
instance.day = day
instance.teacher = ...
instance.save()
return HttpResponseRedirect(...)
Template to render
<form class="form-material m-t-40 floating-labels" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ day_form }} <br>
{{ formset.management_form }}
{% for form in formset %}
{{ form }}<br><br>
{% endfor %}
<div class="form-group row">
<button type="submit" class="btn waves-effect waves-light btn-rounded btn-success">
Submit
</button>
</div>
</form>
In your CreateView you could override get_initial method:
def get_initial(self):
self.initial = CreateView.get_initial(self)
self.initial["classteacher"] = self.request.user
return self.initial.copy()
Then you need to add this field to your form
class Timetableform(ModelForm):
class Meta:
model = Timetable
fields = ( 'start', 'end', 'period', “classteacher”)
if you dont want to show this field in your form and still add classteacher as a current user you could use Hiddeninput widget with this field:
class Timetableform(ModelForm):
class Meta:
model = Timetable
fields = ( 'start', 'end', 'period', “classteacher”)
widgets = {"classteacher": HiddenInput}
in the formset you could use hiddeninput widget as well or use form=Timetableform argument in the constructor. Same idea
I created a formset that has maximum of 5 images to be attached;
1 - but I want the Validation to run only when the user has not attached any image(ValidationError('atleast 1 image is required')),
2- This program is also not allowing the User to save when 1, or 2, or 3 images are attached, which I really need. So if there is 1 image or 2, that should be allowed to save.
3 - I also need to make the 1 radio-button to be selected by default, to make the selected image to be the one dispalyed in the template
template
<form enctype="multipart/form-data" action="" method="post"> {% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{{ form.as_p }}
{{ formset.management_form }}
<div class="link-formset">
{% for choice in formset %}
<div>
<label>{{ choice.media }}</label>
<input type="radio" name="featured">{{choice.featured_image.label }}
</div>
{% endfor %}
</div>
<input type="submit" value="{{ submit_btn }}">
</form>
forms.py
class ProductImagesForm(forms.ModelForm):
media = forms.ImageField(label='Image', required=True)
featured_image = forms.BooleanField(initial=True, required=True)
class Meta:
model = ProductImages
fields = ['media', 'featured_image']
ImagesFormset = modelformset_factory(ProductImages, fields=('media', 'featured_image'), extra=5)
views.py
def form_invalid(self, form, formset):
return self.render_to_response(self.get_context_data(form=form, formset=formset))
def form_valid(self, form, formset):
return HttpResponseRedirect(self.get_success_url())
def get(self, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = ImagesFormset(queryset=ProductImages.objects.none())
return self.render_to_response(self.get_context_data(form=form, formset=formset))
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = ImagesFormset(self.request.POST, self.request.FILES or None)
form_valid = form.is_valid()
formset_valid = formset.is_valid()
if (form.is_valid() and formset.is_valid()):
seller = self.get_account()
form.instance.seller = seller
self.object = form.save()
images_set = formset.save(commit=False)
for img in images_set:
img.product = self.object
img.save()
formset.save()
return self.form_valid(form, formset)
else:
return self.form_invalid(form, formset)
You can always pass in min_num and max_num to the modelformset_factory
ImagesFormset = modelformset_factory(ProductImages,
fields=('media', 'featured_image'),
min_num=1,
max_num=5,
extra=5)
This will ensure that there is at least 1 image and max 5
I'm writing a django app, and I'd like for users to be able to select a [team_number] from a dropdown menu, then when they hit submit be redirected to a page that renders out the database information associated with that selection. I'm using the redirect class View, but the problem I'm having is that there is no dropdown menu showing up to select [team_number] from on the html page team-stats.html.
views.py:
class TeamStatsView(View):
def get(self, request, *args, **kwargs):
return render(request, 'team-stats.html',
{'team_number': TeamStats()})
def post(self, request, *args, **kwargs):
team_number = TeamStats(request.POST, request.FILES)
if team_number.is_valid():
# do stuff & add to database
team_number.save()
team_number = TeamStats.objects.create()
# use my_file.pk or whatever attribute of FileField your id is
# based on
return HttpResponseRedirect('/team-stats/%i/' % team_number.pk)
return render(request, 'team-stats.html', {'team_number': team_number})
models.py:
class Team(models.Model):
team_number = models.IntegerField()
team_notes = models.CharField(max_length=150)
event_id = models.ForeignKey(
'Event', on_delete=models.CASCADE, unique=False)
def __unicode__(self):
return str(self.team_number)
class Meta:
db_table = 'teams'
app_label = 'frcstats'
forms.py:
class TeamStats(forms.ModelForm):
class Meta:
model = Team
fields = ['team_number']
team-stats.html:
<form method="post" action="">
{% csrf_token %} {{ TeamStatsView }}
<input type="submit" value="Submit" />
</form>
If there are any other files that I need to update into here to show what I'm trying to do, please let me know. Thanks
Try changing your view variable name to team_numbers and replacing your team-stats.html snippet with the following:
<form method="post" action="">
<select name="teams">
{% for team_number in team_numbers %}
<option value="{{ team_number }}">Team Num: {{ team_number }}</option>
{% endfor %}
</select>
</form>
Then update your view to:
class TeamStatsView(View):
def get(self, request, *args, **kwargs):
return render(request, 'team-stats.html',
{'team_numbers':Team.objects.values('team_number')})
You can use choices=NUMBERS
NUMBERS = (
('1','1'),
('2','2'),
('3','3'),
('4','4')
)
class Team(models.Model):
team_number = models.IntegerField(choices=NUMBERS )
team_notes = models.CharField(max_length=150)
event_id = models.ForeignKey(
'Event', on_delete=models.CASCADE, unique=False)
def __unicode__(self):
return str(self.team_number)
class Meta:
db_table = 'teams'
app_label = 'frcstats'
Your view variable is called team_number.
Try to change TeamStatsView into team_number:
<form method="post" action="">
{% csrf_token %} {{ team_number }}
<input type="submit" value="Submit" />
</form>