Two fields related in Django - django

I need to update my table every time a new value of "sku" is entered (not to create a new entry), but it does have to happen only if the "client" selected is the same. If the "client" is different, then the model should add a new object with the same "sku", but with different "clients".
I have tried to do the following in my models.py:
class ProductList(models.Model):
id_new = models.IntegerField(primary_key=True)
sku = models.CharField(primary_key=False, max_length=200)
client = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
name = models.CharField(max_length=256)
description = models.CharField(max_length=1000)
storage = models.CharField(max_length=256)
cost_price = models.CharField(max_length=256)
sell_price = models.CharField(max_length=256)
ncm = models.CharField(max_length=256)
inventory = models.IntegerField(null=True)
class Meta:
unique_together = (('sku', 'client'),)
But it is not working. How can I make that work?

You can try like this:
# form
class MyForm(forms.ModelForm):
class Meta:
model = ProductList
def save(self, *args, **kwargs:
client = self.cleaned_data.get('client') # get client from form cleaned_data
if hasattr(self.instance, 'pk') and self.instance.client != client: # check if client match's already existing instance's client
self.instance.pk = None # make a duplicate instance
self.instance.client = client # change the client
return super(MyForm, self).save(*args, **kwargs)
# views.py
# ...
def my_view(request, id):
instance = get_object_or_404(ProductList, id=id)
form = MyForm(request.POST or None, instance=instance)
if form.is_valid():
form.save()
return redirect('next_view')
return render(request, 'my_template.html', {'form': form})
Update
Um you can override the model as well. you can try like this:
# Untested Code but should work
def save(self, *args, **kwargs):
if self.pk:
current_instance = self.__class__.objects.get(pk=self.pk)
if current_instance.client != self.client:
self.pk = None
return super(ProductList, self).save(*args, **kwargs)

Related

Django REST Serializers - passing a value when creating a new object

I'm having problems in passing an argument when creating an object with Django REST serializers.
models.py
class Project(models.Model):
name = models.CharField(max_length=200, unique=False)
description = models.TextField()
...
class Hypothesis(models.Model):
hypothesis = models.CharField(max_length=200, unique=False)
project = models.ManyToManyField(Project)
test_conducted = models.ManyToManyField('Interview', through='HypothesesFeedback')
...
serializers.py
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ['name','description','company_name']
def __init__(self, *args, **kwargs):
super(ProjectSerializer, self).__init__(*args, **kwargs)
class HypothesisSerializer(serializers.ModelSerializer):
class Meta:
model = Hypothesis
fields = ['hypothesis','area','details', 'project']
def get_alternate_name(self, obj):
project = self.context["project_id"]
views.py
class ProjectRestCreate(LoginRequiredMixin, generics.ListCreateAPIView):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
...
class HypothesisRestCreate(LoginRequiredMixin, generics.ListCreateAPIView):
queryset = Hypothesis.objects.all()
serializer_class = HypothesisSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context["project_id"] = 8 #self.kwargs['project_id']
return context
...
I'm currently unable to default the project id when creating a new object for class hypothesis. In the example above, I'm hardcoding a value just for test purposes, but what I'd need to reach is that when I create a new hypothesis starting from a given project page, the project is automatically filled, rather than the user having to manually select it.
Using Django, rather than Django REST, I'd be able to achieve that using the code below:
class HypothesisCreate(generic.CreateView):
model = Hypothesis
form_class = HypothesisForm
template_name = 'new_hypothesis.html'
def form_valid(self, form):
obj = form.save()
project = form.data['project']
p = Project.objects.filter(id=project)
obj.project.set(p)
return super(HypothesisCreate, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(HypothesisCreate, self).get_context_data(**kwargs)
context['p_id'] = self.kwargs['project']
return context
def get_success_url(self, **kwargs):
return reverse('project_detail', kwargs={'pk': self.kwargs['project']})
Any idea on how to reach the same with Django REST serializers?
EDIT #1
models.py
class Project(models.Model):
name = models.CharField(max_length=200, unique=False)
description = models.TextField()
...
class Hypothesis(models.Model):
hypothesis = models.CharField(max_length=200, unique=False)
project = models.ForeignKey(Project, on_delete= models.CASCADE)
test_conducted = models.ManyToManyField('Interview', through='HypothesesFeedback')
...
using Django rather than Django REST, I achieve the defaulting of the project when creating a new hypothesis, using get_context_data:
VIEW:
class HypothesisCreate(generic.CreateView):
model = Hypothesis
form_class = HypothesisForm
template_name = 'new_hypothesis.html'
def form_valid(self, form):
obj = form.save()
project = form.data['project']
p = Project.objects.filter(id=project)
obj.project.set(p)
return super(HypothesisCreate, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(HypothesisCreate, self).get_context_data(**kwargs)
context['p_id'] = self.kwargs['project']
return context
def get_success_url(self, **kwargs):
return reverse('project_detail', kwargs={'pk': self.kwargs['project']})
FORM:
class HypothesisForm(ModelForm):
class Meta:
model = Hypothesis
fields = ['hypothesis','area','details']
def __init__(self, *args, **kwargs):
super(HypothesisForm, self).__init__(*args, **kwargs)
self.fields["project"] = forms.CharField(widget=forms.HiddenInput())
I tried doing the same with the serializer, but without success.
VIEW:
class HypothesisRestCreate(LoginRequiredMixin, generics.ListCreateAPIView):
queryset = Hypothesis.objects.all()
serializer_class = HypothesisSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context["project_id"] = 8 #self.kwargs['project_id']
return context
SERIALIZER:
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ['name','description','company_name']
def __init__(self, *args, **kwargs):
super(ProjectSerializer, self).__init__(*args, **kwargs)
class HypothesisSerializer(serializers.ModelSerializer):
class Meta:
model = Hypothesis
fields = ['hypothesis','area','details', 'project'] #
def get_alternate_name(self, obj):
project = self.context["project_id"]
any idea what should I do differently?
Modify the HypothesisRestCreate as following
class HypothesisRestCreate(LoginRequiredMixin, generics.ListCreateAPIView):
queryset = Hypothesis.objects.all()
serializer_class = HypothesisSerializer
def create(self, request, *args, **kwargs):
request.data['project'] = self.kwargs['project_id']
return super(HypothesisRestCreate, self).create(request, *args, **kwargs)
# def get_serializer_context(self): -- dont need for this purpose
and HypothesisSerializer as following
class HypothesisSerializer(serializers.ModelSerializer):
class Meta:
model = Hypothesis
fields = ['hypothesis','area','details', 'project']
# def get_alternate_name(self, obj): --dont need for this purpose

filtering relational models inside django forms

i have a model which has a foreign key relation with two oder models one of them is 'level'.
the view knows in which level you are based on a session variable,
and then filter the lessons
this is the lesson model:
class Lesson(models.Model):
level = models.ForeignKey(Level,on_delete=models.CASCADE)
subject = models.ForeignKey(Subject,on_delete=models.CASCADE)
chapiter = models.CharField(max_length=200)
lesson = models.CharField(max_length=200)
skill = models.CharField(max_length=200)
vacations = models.IntegerField()
link = models.URLField(max_length=700,null=True,blank=True)
remarques = models.TextField(null=True,blank=True)
order = models.IntegerField()
created = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now=True)
state = models.BooleanField(default=False)
now this is my cbv to create a new lesson:
class GlobalLessonView(CreateView):
model = Lesson
form_class = GlobalLessonForm
success_url = reverse_lazy('globalform')
and this is the form:
class GlobalLessonForm(forms.ModelForm):
class Meta:
model = Lesson
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['subject'].queryset = Subject.objects.none() #change to .all() to see list of all subjects
if 'level' in self.data:
try:
level_id = int(self.data.get('level'))
self.fields['subject'].queryset = Subject.objects.extra(where=[db_name+'.scolarité_subject.id in( select subject_id from '+db_name+'.scolarité_levelsubject where level_id='+level_id+')'])
except (ValueError, TypeError):
pass # invalid input from the client; ignore and fallback to empty City queryset
elif self.instance.pk:
self.fields['subject'].queryset = self.instance.level.subject_set
one of the main conditions is to filter level by a session variable
but the form does not accept request.session
so is there any way to change the levels that shows up at the form from the class based view,or there any way to pass request.session to form.py
Add this to GlobalLessonView:
def get_form_kwargs(self):
"""Pass request to form."""
kwargs = super().get_form_kwargs()
kwargs.update(request=self.request)
return kwargs
Then change the constructor definition in GlobalLessonForm to:
def __init__(self, request, *args, **kwargs):
Then you will be able to reference request.session in GlobalLessonForm.

ProgrammingError at /cart/address/ column order_useraddress.type does not exist

I am trying to create an eCommerce application. And for useraddress (Billing and shipping ) want something like below. Here have a model called Order and UserAddress , which is
class Order(models.Model):
cart = models.ForeignKey(Cart,on_delete=models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE)
start_date = models.DateTimeField(auto_now_add=True)
ordered_date = models.DateTimeField()
ordered_total = models.PositiveIntegerField()
shipping_price = models.PositiveIntegerField(default=0)
ordered = models.BooleanField(default=False)
billing_address = models.ForeignKey(UserAddress,related_name='billing_address',on_delete=models.CASCADE)
shipping_address = models.ForeignKey(UserAddress,related_name='shipping_address',on_delete=models.CASCADE,default=None)
and
class UserAddress(models.Model):
BILLING = 'billing'
SHIPPING = 'shipping'
ADDRESS_TYPE = (
(BILLING , 'Billing'),
(SHIPPING, 'Shipping')
)
user = models.ForeignKey(UserCheckout,
on_delete=models.CASCADE)
name = models.CharField(max_length=50)
phone = models.CharField(max_length=21,null=True)
street_address = models.CharField(max_length=50)
home_address = models.CharField(max_length=50)
type = models.CharField(max_length=100,choices=ADDRESS_TYPE)
def __str__(self):
return self.user
def get_full_address(self):
return '{0}, {1},{2}'.format(self.name ,self.user,self.phone )
And my View is
class AddressFormView(FormView):
form_class = AddressForm
template_name = 'orders/address_select.html'
def dispatch(self, request, *args, **kwargs):
b_address, s_address = self.get_address()
if not (b_address.exists() and s_address.exists()):
messages.success(self.request, 'Please add an address before continuing')
return redirect('add_address') # redirect before checkout
return super(AddressFormView, self).dispatch(request, *args, **kwargs)
def get_address(self, *args, **kwargs):
user_checkout = self.request.session['user_checkout_id']
b_address = UserAddress.objects.filter(
type=UserAddress.BILLING, user_id=user_checkout)
s_address = UserAddress.objects.filter(
type=UserAddress.SHIPPING, user_id=user_checkout)
return b_address, s_address
def get_form(self):
form = super(AddressFormView, self).get_form()
b_address, s_address = self.get_address()
form.fields['billing_address'].queryset = b_address
form.fields['shipping_address'].queryset = s_address
return form
def form_valid(self, form, *args, **kwargs):
billing_address = form.cleaned_data['billing_address']
shipping_address = form.cleaned_data['shipping_address']
self.request.session['billing_address_id'] = billing_address.id
self.request.session['shipping_address_id'] = shipping_address.id
return super(AddressFormView, self).form_valid(form, *args, **kwargs)
def get_success_url(self):
return reverse('checkout')
And the above view used a form , which is
class AddressForm(forms.Form):
billing_address = forms.ModelChoiceField(queryset=UserAddress.objects.filter(type=UserAddress.BILLING),empty_label=None,widget=forms.RadioSelect)
shipping_address = forms.ModelChoiceField(queryset=UserAddress.objects.filter(type=UserAddress.SHIPPING),empty_label=None,widget=forms.RadioSelect)
But Now when i fetching the url http://127.0.0.1:8000/cart/address/ i have the above error. The code is form a github repo which is working correctly. But in my project i am using django Allauth package and Custom User Model, which the github project that i am following didn't use. I assume , this could be an issue , because the github repo didn't use that. So is there any hints or idea or any new way to do it.
The table order_useraddress in your database does not have the type column.
Have you tried migrating/updating the database?
python manage.py makemigrations
python manage.py migrate

How to create or update value to relationship model

I want to save the Portfolio products details in PortfolioProducts model in django
I have models like below:
class Product(models.Model):
name = models.CharField(max_length=255,null=True, verbose_name ='Name')
class Portfolio(models.Model):
name = models.CharField(max_length=100, blank=True, null=True, verbose_name ='Name')
class PortfolioProducts(models.Model):
portfolio = models.ForeignKey(Portfolio, on_delete=models.CASCADE, verbose_name ='Portfolio')
product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name ='Product')
Portfolio form:
class PortfolioForm(forms.ModelForm):
class Meta:
model = Portfolio
fields = ['name']
My view file:
def edit(request):
portfolio_form = PortfolioForm
if request.method=="POST":
portfolio_id=request.POST.get('portfolio_id')
portfolio_detail = Portfolio.objects.get(pk=portfolio_id)
pform = portfolio_form(request.POST, instance=portfolio_detail)
if pform.is_valid():
portfolio = pform.save(commit = False)
portfolio.save()
products=request.POST.getlist('product_id[]')
for product in products:
ppform = PortfolioProducts(product_id=product, portfolio_id=portfolio_id)
port_product = ppform.save()
I am trying to save and update the Portfolio products like this, but is adding products to portfolio multiple time.
Well, you don't need to update PortfolioProduct for updating Portofilio. Because even if you update Portfolio, its primary key remains same as before. So the relationship remains the same.
But, in your case, if PortofolioProduct does not exist for a product in products and Portfolio object, then you can create one like this:
for product in products:
ppform, _ = PortfolioProducts.objects.get_or_create(product_id=product, portfolio_id=portfolio_id)
Update
From comments: you need to either remove def save(self): methods from you Model(Because you are not doing anything particular in those save methods) or if intend to keep you save() methods, then you need to call the super properly, like this:
class Product(models.Model):
name = models.CharField(max_length=255,null=True, verbose_name ='Name')
def save(self, *args, **kwargs):
super(Product, self).save(*args, **kwargs)
class Portfolio(models.Model):
name = models.CharField(max_length=100, blank=True, null=True, verbose_name ='Name')
def save(self, *args, **kwargs):
super(Portfolio, self).save(*args, **kwargs)
class PortfolioProducts(models.Model):
portfolio = models.ForeignKey(Portfolio, on_delete=models.CASCADE, verbose_name ='Portfolio')
product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name ='Product')
def save(self, *args, **kwargs):
super(PortfolioProducts, self).save(*args, **kwargs)
Yes, I also got stuck with the same issue in my django project. The thing it does in my case was everytime the user tries to update his/her profile, it created a new one, this is because of the Foreign Key to it. I fixed the issue by deleting the previous user profile (in your case it's portfolio) every time the user updates it.
class UserEdit(TemplateView):
template_name = 'accounts/homee.html'
def get(self, request):
form = UserProfilee()
ppp = UserProfile.objects.get(user=request.user)
return render(request, self.template_name, {'form': form, 'ppp': ppp})
def post(self, request):
form = UserProfilee(request.POST, request.FILES)
pppp = UserProfile.objects.get(user=request.user)
if form.is_valid():
post = form.save(commit=False)
post.user = request.user
if not post.image:
post.image = pppp.image
UserProfile.objects.filter(user=post.user).delete()
post.save()
return redirect('/home/homepage/')
args = {'form': form}
return render(request, self.template_name, args)
As you see,I filter the user and delete the user profile whenever user updates his/her profile thus leaving only 1 user profile.

Migrate form to modelform

I wrote following form:
class VoteForm(forms.Form):
choice = forms.ModelChoiceField(queryset=None, widget=forms.RadioSelect)
def __init__(self, *args, **kwargs):
question = kwargs.pop('instance', None)
super().__init__(*args, **kwargs)
if question:
self.fields['choice'].queryset = question.choice_set
class VoteView(generic.UpdateView):
template_name = 'polls/vote.html'
model = Question
form_class = VoteForm
def get_queryset(self):
return Question.objects.filter(pub_date__lte=timezone.now()).exclude(choice__isnull=True)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Check duplicate vote cookie
cookie = self.request.COOKIES.get(cookie_name)
if has_voted(cookie, self.object.id):
context['voted'] = True
return context
def get_success_url(self):
return reverse('polls:results', args=(self.object.id,))
def form_valid(self, form):
redirect = super().form_valid(form)
# Set duplicate vote cookie.
cookie = self.request.COOKIES.get(cookie_name)
half_year = timedelta(weeks=26)
expires = datetime.utcnow() + half_year
if cookie and re.match(cookie_pattern, cookie):
redirect.set_cookie(cookie_name, "{}-{}".format(cookie, self.object.id), expires=expires)
else:
redirect.set_cookie(cookie_name, self.object.id, expires=expires)
return redirect
The problem is that the normal form does not represent a object does not have the save() method like ModelForm. But I can't figure out how to migrate the form. There is no choice or choice_set field:
class VoteForm(forms.ModelForm):
class Meta:
Model = Question
#throws exception
fields = ('choice',)
widgets = {
'choice': forms.RadioSelect()
}
EDIT:
Here are the models:
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
How can the form from above be reproduced as a modelform?
Even if you have ModelForm you can define additional fields if you need them. In your case it will be choice field as in previous normal form.
Then in Meta you will exclude all the fields of the Question model which are not required.
After that in init you will provide choice set of provided instance to the choice field.
class VoteForm(forms.ModelForm):
choice = forms.ModelChoiceField(queryset=None, widget=forms.RadioSelect)
class Meta:
model = Question
exclude = ['question_text','pub_date']
def __init__(self, *args, **kwargs):
super(VoteForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance:
self.fields['choice'].queryset = instance.choice_set
The code is written online and not tested, but I think it should work.