How to get all fields of related object Django - django

I have two models:
class Checklist(models.Model):
author = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
client_name = models.ForeignKey(Client, on_delete=models.CASCADE,
related_name='details')
fieldA = models.CharField(max_length=25, blank=True, null=True)
fieldA_Check= models.BooleanField(default=False)
fieldB = models.CharField(max_length=25, blank=True, null=True)
fieldB_Check= models.BooleanField(default=False)
class Client(models.Model):
client_fieldA = models.CharField(max_length=25)
client_fieldB = models.CharField(max_length=25)
client_fieldC = models.CharField(max_length=25)
Now I trying to change Client instance fields values via Checklist using following code:
#login_required
def create_order(request, pk):
instance = get_object_or_404(CheckList, id=pk)
form = myForm(request.POST or None, instance=instance)
if form.is_valid():
form.save()
messages.success(request, 'Test message')
return get_PDF_order(request, pk)
return render(request, 'templates/create_order.html', {'form': form})
here is my form:
class myForm(forms.ModelForm):
class Meta:
model = Client
fields = ('client_fieldA', 'client_fieldB', 'client_fieldC')
widgets = {
'client_fieldA': forms.TextInput(attrs={'class': 'form-control'}),
'client_fieldB': forms.TextInput(attrs={'class': 'form-control'}),
'client_fieldC': forms.TextInput(attrs={'class': 'form-control'}),
}
And it works but only once. Data saved and I get a pdf file with this data, but subsequent attempts do not change anything. The field value still the same. I'm guessing that it is because I working with the Checklist instance but not Client. So, my questions are: 1) How I can get an access to all Client fields (a queryset of all Client fileds) via Checklist? 2) Although my code is incorrect - why does it work (once)? I mean why does it save first time?
Thank you all in advance!

To formalize the answer, the instance is actually an instance of the model the form is based on. Thus, in your case, you cannot pass a CheckList instance to a form based on Client
Anyway, as far as you have the CheckList id, you can easily obtain the Client, and the code for your view will be:
#login_required
def create_order(request, pk):
instance = get_object_or_404(CheckList, id=pk)
form = myForm(request.POST or None, instance=instance.client_name)
if form.is_valid():
form.save()
messages.success(request, 'Test message')
return get_PDF_order(request, pk)
return render(request, 'templates/create_order.html', {'form': form})

You pass it always the same instance, so what happens is that you are probably overwriting the same record over and over.
I would recommend you to split this using the django-provided generic class-based views CreateView and UpdateView.
Stop reinventing the wheel, use class-based views. It'll pay off big time in the long term.

Related

how to apply constraints on Django field within the instances associated with the user

I am making a todo app for practice, the functionality I want to achieve is that, when a user create a task,Only the time field should be unique about all of his tasks, I have done (unique=True) In the time field in model but that make it unique all over the database, but I want it to be unique only with the tasks associated with the user.
the view is below:
#login_required(login_url='login')
def home(request):
tasks = Task.objects.filter(name__username=request.user.username)
form = TaskForm()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.name = request.user
obj.save()
return redirect('home')
else:
print(request.POST)
print(request.user.username)
messages.warning(request, 'Invalid Data!')
return redirect('home')
context = {'tasks' : tasks}
return render(request, 'task/home.html', context)
task model:
class Task(models.Model):
choices = (
('Completed', 'Completed'),
('In Complete', 'In Complete'),
)
name = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
task = models.CharField(max_length=200, null=False, blank=False)
time = models.TimeField(auto_now_add=False, blank=True)
status = models.CharField(max_length=200, choices=choices, null=True, blank=False)
def __str__(self):
return self.task
def get_task_author_profile(self):
return reverse('profile')
as you can see, I want to show the task that the logged in user has added.
the form is:
class TaskForm(ModelForm):
class Meta:
model = Task
fields = '__all__'
exclude = ['name']
the functionality I talked about above, I tried to achieve through view:
#login_required(login_url='login')
def home(request):
tasks = Task.objects.filter(name__username=request.user.username)
time = []
for task in tasks:
time.append(task['time'])
form = TaskForm()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid() and form.cleaned_data['time'] != time:
obj = form.save(commit=False)
obj.name = request.user
obj.save()
return redirect('home')
else:
print(request.POST)
print(request.user.username)
messages.warning(request, 'Invalid Data!')
return redirect('home')
context = {'tasks' : tasks}
return render(request, 'task/home.html', context)
but that gave an error: TypeError: 'Task' object is not subscriptable
I know its not right, but how can I achieve it, does Django have anything that can provide such fuctionality?
The problem is coming from here:
for task in tasks:
time.append(task['time']) #<--
Here if you want to use access time, you need to use task.time because task is an object.
Also need to fix another thing in your exisiting code to make it work, because time is a list:
if form.is_valid() and form.cleaned_data['time'] in time:
# ^^^
BTW, you don't need to make it that complicated, you can add Database level constraint from the model to make the times unique for a specific user. Also, use DateTime field for that. You can use unique_togather for that:
class Task(models.Model):
choices = (
('Completed', 'Completed'),
('In Complete', 'In Complete'),
)
name = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
task = models.CharField(max_length=200, null=False, blank=False)
time = models.DateTimeField(auto_now_add=False, blank=True)
status = models.CharField(max_length=200, choices=choices, null=True, blank=False)
class Meta:
unique_togather = ['name', 'time']

Django Formset: how to get the current user? (using django-extra-views)

I'm used to collecting the current logged in user in a CreateView and passing it to the form like so:
class MakeFantasyTeam(CreateView):
form_class = MakeFantasyTeamForm
[...]
def form_valid(self, form):
form.instance.team_manager = self.request.user
form.save()
return super(MakeFantasyTeam, self).form_valid(form)
However, this doesn't seem to work when using an InlineFormSetView as provided by django-extra-views. I get an error NOT NULL constraint failed: tournament_invite.invited_by_id and I'm not sure how to get the user.id passed on to the form.
My View:
class InvitePlayersView(InlineFormSetView):
template_name = 'invite_players.html'
model = Tournament
inline_model = Invite
form_class = InvitePlayerForm
pk_url_kwarg = 'tourney_id'
factory_kwargs = {'can_delete': False, 'extra': 1}
def formset_valid(self, formset):
tourney_id = self.kwargs['tourney_id']
formset.instance.invited_for = Tournament.objects.filter(id=tourney_id).get()
formset.instance.invited_by = self.request.user
formset.save()
return super(InvitePlayersView, self).formset_valid(formset)
def get_success_url(self):
return reverse('make_team', kwargs={'tourney_id': self.object.invited_for.id})
My Model:
class Invite(models.Model):
name = models.CharField(max_length=200, blank=True, null=True)
email = models.CharField(max_length=320, null=False, blank=False, validators=[EmailValidator],)
invited_by = models.ForeignKey(get_user_model(), on_delete=models.DO_NOTHING)
invited_for = models.ForeignKey(Tournament, on_delete=models.DO_NOTHING)
created_dt = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.email
def get_absolute_url(self):
return reverse('home')
My Form:
class InvitePlayerForm(forms.ModelForm):
class Meta:
model = Invite
fields = ('name', 'email',)
Any tips or hints much appreciated!
Thank you,
Jon
Edit: Just to clarify what I'm trying to do here; I want a user to submit a formset. The data of that formset should be stored in the model, and the userid of the submitting user should also be stored in the model. I don't seem to be able to pass on the userid though.
I am not sure what you exactly want to do here, As per my understanding you want to use the currently logged in user's information. To do so you can append the user's info in the session dictionary. After that you can use the information in templates or in other views too.
In authentication view
def login(request):
#your necessary data
request.session['user_id']=The_user_id
request.session['user_name']=The_userName
To access data in the template
{% request.session.user_id %}
{% request.session.user_name %}
To access data in other views
def myview(request):
user_id= request.session['user_id']
user_name= request.session['user_name']

With Django, how do I reference an existing model in a form save method instead of creating a new instance?

I'm trying to use a ModelChoiceField to display options populated from model, and when a user selects a choice, store that method in a different model.
I'm using a standard form instead of a ModelForm, because I wasn't able to get the form to display how I wanted to when using a Modelform.
My issue is that in my form save method, a new instance is created, which is not what I want.
Here are the relevant models:
class Client(models.Model):
client_email = models.EmailField(max_length = 254)
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
phone = PhoneField(blank=True)
assigned_manager = models.ForeignKey(Manager, on_delete=models.CASCADE, blank=True, null=True)
created_date = models.DateTimeField(default=timezone.now)
#property
def full_name(self):
return '{0} {1}'.format(self.first_name, self.last_name)
class Manager(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
manager_email = models.EmailField(max_length = 254)
username = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
#property
def full_name(self):
return '{0} {1}'.format(self.first_name, self.last_name)
My view:
def manageclient(request, urlid):
client = Client.objects.get(id=urlid)
form = AssignManagerForm()
if request.method == "POST":
form = AssignManagerForm(request.POST)
if form.is_valid():
form.save()
return render(request, 'mysite/manageclient.html', {})
else:
form = AssignManagerForm()
context = {
'client': client,
'urlid': urlid,
'form': form,
}
return render(request, 'mysite/manageclient.html', context)
And my forms.py
class AssignManagerForm(forms.Form):
full_name = forms.ModelChoiceField(queryset=Manager.objects.all())
def save(self):
data = self.cleaned_data
client = Client(assigned_manager=data['full_name'])
client.save()
What I need to do is pass the urlid in my view to my save method in my forms.py, but I am unsure how to do that. Even if i could do that, I'm not sure how to modify form save to use urlid to refer to a specific record and set only the assigned_manager record.
Additionally, while I want the meta field to be used to display the form, I know it isn't what should be being passed to the assigned_manager field. How would I pass a Manager of instance to establish the foreign key relationship?
edit: edited to correct queryset in forms.py as per comments
Here is a solution using a ModelForm, by using a ModelForm you no longer have to manually set attributes on save or provide initial values when updating an existing instance.
The field assigned_manager will still be named assigned_manager but it's label can be overridden to be whatever you want it to be by passing labels in the ModelForm.Meta
class AssignManagerForm(forms.ModelForm):
class Meta:
model = Client
fields = ['assigned_manager']
labels = {'assigned_manager': 'Full name'}
def manageclient(request, urlid):
client = Client.objects.get(id=urlid)
if request.method == "POST":
form = AssignManagerForm(request.POST, instance=client)
if form.is_valid():
client = form.save()
# The general convention is to redirect after a successful POST
else:
form = AssignManagerForm(instance=client)
context = {
'client': client,
'urlid': urlid,
'form': form,
}
return render(request, 'mysite/manageclient.html', context)
Instead of saving it in form, you can directly do this operation in view. For example:
def manageclient(request, urlid):
client = Client.objects.get(id=urlid)
if request.method == "POST":
form = AssignManagerForm(request.POST)
if form.is_valid():
client.assigned_manager = form.cleaned_data['full_name']
client.save()
return render(request, 'mysite/manageclient.html', {})

Django UserChangeForm custom validation

ValueError: The Custom_User could not be changed because the data didn't validate.
I am trying to make a UserChangeForm to allow customers to edit their address, contact or password. However, for the field "postal code" I have set a restriction where there are only certain postal addresses I want to service.
#forms.py
#Extract list of postal codes which are valid and put into a list
valid_postal_code = []
postal_code_model = PostalCode.objects.all()
for code in postal_code_model:
valid_postal_code.append(code.postal_code)
#form
class EditAccountForm(UserChangeForm):
class Meta:
model = Custom_User
fields = (
'address',
'postal_code',
'unit_number',
'email',
'contact_number',
'password'
)
def clean_postal_code(self):
post_code = self.cleaned_data.get('postal_code')
if post_code not in valid_postal_code:
print('hello')
raise forms.ValidationError('Sorry we do not serve this postal code right now D:')
return post_code
If the user inputs a postal code that is not in the valid_postal_code list, I would like that the form be able to raise an Error message on the form.
However, I get the above error(which is to be expected), straight away without the raising for error.
#views.py
def edit_info_page(request):
if request.method == 'POST':
form = EditAccountForm(request.POST, instance=request.user)
if form.is_valid:
form.save()
print('form changed')
return redirect('home:edit_info_page')
else:
form = EditAccountForm(instance=request.user)
return render(request, 'sign/edit_info.html', {'form':form})
#models
class Custom_User(AbstractUser):
postal_code = models.IntegerField(null=True)
unit_number = models.CharField(max_length=10)
address = models.CharField(max_length=250)
contact_number = models.IntegerField(null=True)
order_count = models.PositiveIntegerField(default=0)
total_spending = models.DecimalField(max_digits=10, decimal_places=2, default=0)
def __str__(self):
return self.username
Above are my models and views for reference. IMO, I think I am definitely missing something here but Im just not too sure how to break into the UserChangeForm. I'm still a relative newbie (haven't sent anything into production yet). Any advice would be great!
In the view change:
if form.is_valid:
to
if form.is_valid():
The validation is not getting executed, which consequently does not validate your post data, which in turn does not allow you to save the user.
Suggestion:
Change
if post_code not in valid_postal_code:
to
if post_code and post_code not in valid_postal_code:
to ensure to raise the error only if user has entered something.
Try this
class EditAccountForm(UserChangeForm):
class Meta:
model = Custom_User
fields = (
'address',
'postal_code',
'unit_number',
'email',
'contact_number',
'password'
)
def clean_postal_code(self):
valid_postal_code = PostalCode.objects.all().values_list('postal_code', flat=True)
post_code = self.cleaned_data.get('postal_code')
if post_code not in valid_postal_code:
raise forms.ValidationError('Sorry we do not serve this postal code right now D:')
return post_code
Better way to write AJAX call for postal_code field and call ajax function from postal_code focus out/blur event,
Or Make the postal code field auto complete or selection field

Django Form Not Saving, Terminal Shows Data & Save Method Added

All I'm trying to do is save the simple form. Everything looks fine but after I click save and the form is re-rendered there is no new trade in the Database. No error message is thrown either.
At first, I thought there was an issue with the user but it looks fine as well. Been reading a lot of documentation on this topic but haven't found where the issue is yet.
Thanks for any help and please let me know if there is anything extra I can add.
create.html
<form id='trade_create_view' method='POST' action='.'>
{% csrf_token %}
{{ form.as_p }}
<input type='submit' value='Submit' >
</form>
views.py
def trade_create_view(request):
form = TradeForm(request.POST or None, instance=request.user)
if form.is_valid():
print(form.cleaned_data)
form.save()
form = TradeForm()
context = {
'form': form,
}
return render(request, "tj/cp/trade/create.html", context)
forms.py
from django import forms
from .models import Trade
class TradeForm(forms.ModelForm):
class Meta:
model = Trade
fields = [
'user',
'target_size',
'target_entry',
'target_exit',
'ticker',
'exchange',
'short',
'comments',
'size',
'entry_price',
'exit_price',
'entry_date',
'exit_date',
'fees',
'permission',
]
exclude = ['user',]
model.py
class Trade(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=False)
comments = models.TextField(max_length=10000, blank=True, null=True)
created = models.DateField(auto_now_add=True)
last_edit = models.DateField(auto_now=True)
#general trade info
ticker = models.ForeignKey(Ticker, on_delete=models.CASCADE)
short = models.BooleanField(default=False)
exchange = models.ForeignKey(Exchange, on_delete=models.CASCADE)
#target trade outline
target_size = models.DecimalField(null=True, blank=True, max_digits=50, decimal_places=20)
target_entry = models.DecimalField(null=True, blank=True, max_digits=50, decimal_places=20)
target_exit = models.DecimalField(null=True, blank=True, max_digits=50, decimal_places=20)
#real trade
size = models.DecimalField(null=True, blank=True, max_digits=50, decimal_places=20)
entry_price = models.DecimalField(null=True, blank=True, max_digits=50, decimal_places=20)
exit_price = models.DecimalField(null=True, blank=True, max_digits=50, decimal_places=20)
entry_date = models.DateField(blank=True, null=True, default=datetime.now)
exit_date = models.DateField(blank=True, null=True, default=datetime.now)
fees = models.DecimalField(blank=True, null=True, max_digits=50, decimal_places=20)
PER_OPTIONS = [
('0', 'Public'),
('1', 'Private'),
('2', 'Mentor Only'),
]
permission = models.CharField(max_length=1, choices=PER_OPTIONS, default=0)
I think you just have to check if your request is a post or a get method:
def trade_create_view(request):
if request.method == "POST":
form = TradeForm(request.POST or None, instance=request.user)
if form.is_valid():
print(form.cleaned_data)
form.save()
form = TradeForm()
context = {
'form': form,
}
return render(request, "tj/cp/trade/create.html", context)
OR
You just can add a post def to your view as follows:
def post(self, request):
form = TradeForm(request.POST or None, instance=request.user)
if form.is_valid():
print(form.cleaned_data)
form.save()
form = TradeForm()
...
This line doesn't make sens: TradeForm(request.POST or None, instance=request.user)
When you use instance for your form, you give an instance of the object concerned. But your object is Trade not User. You can choose different ways to solve the problem, I give one:
def trade_create_view(request, id):
form = TradeForm()
if request.method == "POST":
trade, created = Trade.objects.get_or_create(id=id) # you get or create your instanced Trade
form = TradeForm(request.POST, instance=trade) # you give to your form the instance of your Trade
if form.is_valid():
print(form.cleaned_data)
form.save()
context = {
'form': form,
}
return render(request, "tj/cp/trade/create.html", context)
I arbitrarily took the id to query an object Trade. As I understand your code you can use this function to create and edite any Trade object with your TradeForm.
get_or_create is document on the Django Website. If you don't when to use it, you can use Trade.objects.get(id=id) you you need to check if the object exist before.
But if you just when to create an object Trade just remove instance=XXX, and use TradeForm(request.POST). Instance is used to get an object in your database and overwrite with the new data posted and processed by your form.
So I switched everything over to Class-Based Views and for some reason now it works perfectly and instantly.
What came even more as a shock is that when I checked my database again after the first Class-Based View test there were about 15 test trades that now appeared (and trust me I checked/refreshed the database after every function view test). So.. I guess it worked all along which is nice because I really read the documentation 10x to figure out what was wrong and was getting really frustrated. What's not so nice is that the database took hours to update or maybe they were frozen somewhere else. That I can't explain yet.
the new views.py
class TradeCreateView(CreateView):
template_name = "tj/cp/trade/create.html"
form_class = TradeForm
queryset = Trade.objects.all()
def form_valid(self, form):
print(form.cleaned_data)
return super().form_valid(form)
Thanks for the help everyone!
I know this maybe not the perfect resolve you guys were hoping for but at least there is some closure.