So I have models GeneralUser, Business, Brand, Bottle.
class Business(models.Model):
name = models.CharField(max_length=100, blank=False)
owner = models.ForeignKey(GeneralUser, on_delete=models.CASCADE, blank=False, related_name="businesses")
class Brand(models.Model):
name = models.CharField(max_length=150, blank=False, unique=True)
businesses = models.ManyToManyField(Business, related_name="brands", blank=False)
class Bottle(models.Model):
name = models.CharField(max_length=150, blank=False)
slug = models.SlugField(max_length=550, default="", null=False, blank=False)
brand = models.ForeignKey(Brand, on_delete=models.CASCADE, blank=False, related_name="bottles")
Each Brand can belong to many businesses, and each Business can hold many brands.
My goal is to not have Brand objects created if they already exist in the database.
I've built a Bottle ModelForm which also allows the user to choose and create a new Brand in the process.
But the problem I'm facing now is this:
Users only see brands associated with the Business.
User 1 created Brand X → OK
User 2 tries to create Brand X → IntegrityError unique constraint failed.
Which I totally understand and not surprised by. My question is what's the best solution in this case?
The BottleCreateForm is pretty standard:
class BottleCreateForm(ModelForm):
class Meta:
model = Bottle
fields = ['name', 'brand' , 'vintage', 'capacity']
But I'm also adding an "Add New" HTML button, which calls the brand create view:
def add_brand(request):
response_data = {}
if request.method == "POST":
response_data['success'] = False
form = BrandCreateForm(request.POST)
if form.is_valid():
print("form valid!")
users_business = Business.objects.filter(owner=request.user).first()
form.save(commit=True)
users_business.brands.add(form.instance)
response_data['pk'] = form.instance.id
response_data['success'] = True
return HttpResponse(
json.dumps(response_data),
content_type="application/json"
)
else:
response_data['errors'] = form.errors
return HttpResponse(
json.dumps(response_data),
content_type="application/json"
)
Related
I have the following models/forms/view in which I have managed to submit to two different models as follows:
Models
class Account(models.Model):
username = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
name = models.CharField(max_length=150)
actflag = models.CharField(max_length=1, blank=True)
acttime = models.DateTimeField(blank=True, null=True)
comments = models.TextField(_('comments'), max_length=500, blank=True)
def __str__(self):
return self.name
class ISIN(models.Model):
code = models.CharField(max_length=12)
account_name = models.ForeignKey(Account, on_delete=models.SET_NULL, null=True)
actflag = models.CharField(max_length=1, blank=True)
acttime = models.DateTimeField(blank=True, null=True)
def __str__(self):
return self.code
Forms
from apps.portfolio.models import Account, ISIN
class PortfolioForm(forms.ModelForm):
class Meta:
model = Account
fields = ['name', 'comments']
class IdentifierForm(forms.ModelForm):
class Meta:
model = ISIN
fields = ['code']
View
def portfolios(request):
if request.user.is_authenticated:
if request.POST:
fm = PortfolioForm(request.POST)
fm2 = IdentifierForm(request.POST)
if fm.is_valid():
messages.success(request, 'Portfolio has been created.')
account = fm.save(commit=False)
account.username = request.user
account.acttime = timezone.now()
account.actflag = 'I'
account.save()
isin = fm2.save(commit=False)
#isin.account_name = account.name
isin.acttime = timezone.now()
isin.actflag = 'I'
isin.save()
return redirect('portfolios')
else:
fm = PortfolioForm()
fm2 = IdentifierForm()
context = {"name": request.user, "form": fm, "form2": fm2}
return render(request, 'portfolios.html', context)
else:
return redirect('login')
However, you will notice the commented line in my view: isin.account_name = account.name, when I uncomment this line and try to submit the forms again I get the following error: Cannot assign "'test'": "ISIN.account_name" must be a "Account" instance.
I believe it's to do with ForeignKey but still unsure how to store the newly created account name the user submitted within the isin model.
Help is much appreciated.
Although my answer solves the problem you originally had, there are a couple additional points that I wanted to make.
Improve naming and fix the original error
Your field is called account_name, and it implies that a string will be stored there. If it was actually a string, you would be able to do what you tried:
isin.account_name = account.name
In reality, you have a ForeignKey to the Account model, so you have to actually save a reference to the account object:
isin.account_name = account
It's a really good idea to have a foreign key instead of just a string because it avoids denormalization.
The problem here is the name of the field, account_name. If you later want to access the account name, you would have to write something like isis.account_name.name. Sounds wrong, doesn't it?
You could solve this by renaming your field like so:
class ISIN(models.Model):
code = models.CharField(max_length=12)
account = models.ForeignKey(Account, on_delete=models.SET_NULL, null=True)
actflag = models.CharField(max_length=1, blank=True)
acttime = models.DateTimeField(blank=True, null=True)
def __str__(self):
return self.code
Then, in your view, you would just isin.account = account, and later, if you wanted to access the name, you would use isin.account.name.
Another minor thing is that in some places an account is called Account and in other places it's Portfolio. This creates an illusion that they're unrelated entities and makes your code harder to read and maintain.
You probably should decide which one is the better term, and make it consistent everywhere.
Use builtin timestamp mechanism
Looks like you're using the acttime field to manually store creation time of accounts and ISINs.
You could use Django's auto_now_add property to do that automatically, like so:
class Account(models.Model):
acttime = models.DateTimeField(auto_now_add=True)
If you also wanted to store the last time an Account was updated, you could use auto_now (also renamed fields here for clarity):
class Account(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
And to stay DRY, you could make a mixin for that and use it in both Account and ISIN:
class TimeStampMixin(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class Account(TimeStampMixin, models.Model):
username = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
name = models.CharField(max_length=150)
actflag = models.CharField(max_length=1, blank=True)
comments = models.TextField(_('comments'), max_length=500, blank=True)
def __str__(self):
return self.name
class ISIN(TimeStampMixin, models.Model):
code = models.CharField(max_length=12)
account = models.ForeignKey(Account, on_delete=models.SET_NULL, null=True)
actflag = models.CharField(max_length=1, blank=True)
def __str__(self):
return self.code
This way, the creation time and the latest update time are automatically stored in your models (the ones that inherit from TimeStampMixin).
Validate both forms
Looks like you're only checking one of the forms for validity, and not the other:
if fm.is_valid():
You should probably check both, in case ISIN.code is invalid:
if fm.is_valid() and fm2.is_valid():
What it means is that you have to make an instance of the account model by getting the name in order to save the form like so:
def portfolios(request):
if request.user.is_authenticated:
if request.POST:
fm = PortfolioForm(request.POST)
fm2 = IdentifierForm(request.POST)
if fm.is_valid():
messages.success(request, 'Portfolio has been created.')
account = fm.save(commit=False)
account.username = request.user
account.acttime = timezone.now()
account.actflag = 'I'
account.save()
# Here is where we get the instance of account
account = Account.objects.get(name=account.name)
isin = fm2.save(commit=False)
isin.account_name = account
isin.acttime = timezone.now()
isin.actflag = 'I'
isin.save()
return redirect('portfolios')
else:
fm = PortfolioForm()
fm2 = IdentifierForm()
context = {"name": request.user, "form": fm, "form2": fm2}
return render(request, 'portfolios.html', context)
else:
return redirect('login')
The field account_name is a ForeignKey to Account, but you are assigning an string. You should to assign an Account.
Change:
isin.account_name = account.name
To:
isin.account_name = account
I've got the following Situation, I have a rather large legacy model (which works nonetheless well) and need one of its fields as a distinct dropdown for one of my forms:
Legacy Table:
class SummaryView(models.Model):
...
Period = models.CharField(db_column='Period', max_length=10, blank=True, null=True)
...
def __str__(self):
return self.Period
class Meta:
managed = False # Created from a view. Don't remove.
db_table = 'MC_AUT_SummaryView'
Internal Model:
class BillCycle(models.Model):
...
Name = models.CharField(max_length=100, verbose_name='Name')
Period = models.CharField(max_length=10, null=True, blank=True)
Version = models.FloatField(verbose_name='Version', default=1.0)
Type = models.CharField(max_length=100, verbose_name='Type', choices=billcycle_type_choices)
Association = models.ForeignKey(BillCycleAssociation, on_delete=models.DO_NOTHING)
...
def __str__(self):
return self.Name
Since I don't want to connect them via a Foreign Key (as the SummaryView is not managed by Django) I tried a solution which I already used quite a few times. In my forms I create a ModelChoiceField which points to my Legacy Model:
class BillcycleModelForm(forms.ModelForm):
period_tmp = forms.ModelChoiceField(queryset=SummaryView.objects.values_list('Period', flat=True).distinct(),
required=False, label='Period')
....
class Meta:
model = BillCycle
fields = ['Name', 'Type', 'Association', 'period_tmp']
And in my view I try to over-write the Period Field from my internal Model with users form input:
def billcycle_create(request, template_name='XXX'):
form = BillcycleModelForm(request.POST or None)
data = request.POST.copy()
username = request.user
print("Data:")
print(data)
if form.is_valid():
initial_obj = form.save(commit=False)
initial_obj.ModifiedBy = username
initial_obj.Period = form.cleaned_data['period_tmp']
initial_obj.Status = 'Creating...'
print("initial object:")
print(initial_obj)
form.save()
....
So far so good:
Drop Down is rendered correctly
In my print Statement in the View ("data") I see that the desired infos are there:
'Type': ['Create/Delta'], 'Association': ['CP'], 'period_tmp': ['2019-12']
Still I get a Select a valid choice. That choice is not one of the available choices. Error in the forms. Any ideas??
Hi I have the following django model:
class Issue(models.Model):
title = models.CharField(max_length=200)
date = models.DateTimeField(auto_now=True)
assignee = models.ForeignKey(User, on_delete=models.CASCADE, related_name='assignee')
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='owner', null=True, blank=True)
description = models.TextField()
state = models.IntegerField(choices=STATUS_CHOICES, default=1)
priority = models.IntegerField(choices=RELEVANCE_CHOICES, default=2)
expired_date = models.DateField(auto_now=False, null=True, blank=True)
and a form which allow a user to create an Issue instance:
class IssueForm(forms.ModelForm):
class Meta:
model = Issue
fields = ('title', 'description', 'assignee', 'state', 'priority', 'expired_date')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].label = "Titolo"
self.fields['description'].label = "Descrizione"
self.fields['state'].label = "Stato"
self.fields['priority'].label = "Priorità"
self.fields['expired_date'].label = "Termine"
self.fields['expired_date'].widget.attrs.update({'class': 'datepicker'})
self.fields['assignee'] = forms.MultipleChoiceField(
choices=self.fields['assignee'].choices,
widget=forms.CheckboxSelectMultiple,
label=("Assegnatario")
)
def clean(self):
cleaned_data = super().clean()
user_id = [i for i in cleaned_data['assignee']]
cleaned_data['assignee'] = [User.objects.get(id=i) for i in user_id]
return cleaned_data
I render this form and the field assignee is a checkbox.
I would like to be able to choose several assignee for the same issue, but I got an error because the Issue model expect just one User instance
How can I modify my model Issue in order to get more than one user ?
Thanks
you can create a new class and name it Issue_Instance where every Issue Object can have an assignee as a foreign key the problem that the relation is one to many because you have to choose more than one assignee and Django doesn't support the idea of having Array or List of Foreign Keys(I don't know any frame works that do :=) ) so I would suggest creating a new class or make the foreign key relation one-to-many key field read about it it will be very useful to solve your problem
So here, I would like the quantity existing of my Article model to increase when saving the Purchase model,
Here is my code in views.py that does not work!
I am still a beginner in Django. thank you in advance
example:
quantity of article in stock: 20
quantity purchased during a purchase: 5
so in the end in the database I would like to have 25 in the item warren in stock!
sorry for my english, i use google translator
def achat_form_view(request):
if (request.method == 'POST'):
form = AchatForm(request.POST,error_class=ParagraphErrorList)
if form.is_valid():
Article.quantite = Article.quantite + Achat.quantite_a
form.save(commit=True)
return redirect('manapoitra_achat')
else:
form = AchatForm()
return render(request, 'achatH.html', {'form': form})
models.py :
class Achat(models.Model):
id_article_a = models.ForeignKey(Article, on_delete=models.CASCADE)
id_fournisseur_a = models.ForeignKey(Fournisseur, on_delete=models.CASCADE)
quantite_a = models.PositiveIntegerField(max_length=4, verbose_name="Quantité(s)")
date_a = models.DateTimeField(auto_now_add=True, verbose_name="Date de création")
date_save_tara_a = models.DateField(blank=True, null=True)
def __unicode__(self):
return self.pk+' achat'
class Article(models.Model):
photo = models.FileField()
nom = models.CharField(max_length=60, verbose_name="Produit")
type = models.ForeignKey(Type, verbose_name="Type", on_delete=models.CASCADE)
categorie = models.ForeignKey(Categorie, verbose_name="Catégorie", on_delete=models.CASCADE)
prix_de_vente = models.CharField(max_length=8, verbose_name="Prix de vente")
prix_d_achat = models.CharField(max_length=8, verbose_name="Prix d'achat")
quantite = models.PositiveIntegerField(max_length=4, verbose_name="Quantité(s)")
date_a = models.DateTimeField(auto_now_add=True, verbose_name="Date de création")
date_de_perim = models.DateField(blank=True, null=True, verbose_name="Perimé(e) le")
def __str__(self):
return self.nom
The problem with your code is that Achat and Article refer to the entire class, not any specific instance. What you want to do is take the Achat created by your form, and increase the quantity of the specific Article chosen in that form. You can do this via the return value of form.save(), which is an instance of Achat.
if form.is_valid():
achat = form.save()
article = achat.id_article_a
article.quantite += achat.quantite_a
article.save()
return redirect('manapoitra_achat')
(Note, your field naming convention is very strange; there's no need to suffix with _a, but more importantly you should not name ForeignKey fields with an id_ prefix; the Django ForeignKey is not an ID, but gives you access directly to the related object. So for example id_article_a should be just article.)
I have created an Employee class below in my models. The Employee class has multiple Foreign Keys such as User,Contact,Skill etc. I would like to make it possible that when I create an Employee all the other objects will be created plus including the User object. I have implemented a POST method in my view that does this but I feel like my code is too long. How do I make a single POST to create all these multiple objects? An illustration using Managers will be also nice.
class Employee(models.Model):
"""
Model, which holds general information of an employee.
"""
user = models.OneToOneField(settings.AUTH_USER_MODEL,
related_name='employees', null=True)
company = models.ForeignKey(
'hr.Company',
verbose_name='Company',
related_name='companies',
null=True, blank=True,
)
hr_number = models.CharField(
verbose_name='HR number',
blank=True, null=True,
max_length=20, unique=True
)
identification_number = models.CharField(
verbose_name='ID Number',
blank=True, null=True,
max_length=20, unique=True
)
contract_type = models.ForeignKey(Contract)
tax_id_number = models.CharField(
max_length=20, null=True, verbose_name='Tax ID', blank=True, unique=True)
skill = models.ForeignKey(Skill)
# joining can be added in user profile
joining_date = models.DateField(null=True, verbose_name="Joining Date")
job_title = models.ForeignKey(
Job, related_name='job_titles', null=True, blank=True, help_text='Default Permission for different modules in Portal depends upon employee\'s Designation.')
department = models.ForeignKey(
Department, related_name='department', null=True, blank=True, on_delete=models.SET_NULL)
is_manager = models.BooleanField(default=False)
# leave_count = models.IntegerField(default=0)
active = models.BooleanField(default=True)
In my views I have have implemented the POST method below:
class AddEmployee(APIView):
# permission_classes = (permissions.DjangoObjectPermissions,)
# serializer_class = EmployeeSerializer
"""
{
"user":null,
"new_user":{
"first_name":"John",
"last_name":"Wane",
"username":"Wai",
"email":"jwane#gmail.com",
"password":"123"
},
"company":1,
"department":1,
"identification_number":"234567",
"hr_number":"GH/099/2017",
"tax_id_number":"AEEEEEE",
"joining_date":"2018-04-02",
"job_title":null,
"new_job":{
"name":"Doctor",
"min_salary":50000,
"max_salary":50000
}
}
"""
def post(self, request, format=None):
try:
company = Company.objects.get(id=request.data['company'])
department = Department.objects.get(id=request.data['department'])
try:
c_user = User.objects.get(id=request.data['user'])
except:
new_user = request.data['new_user']
c_user = User.objects.create(first_name=new_user['first_name'],
last_name=new_user['last_name'],
username=new_user['username'],
email=new_user['email'],
password=new_user['password'])
try:
job_title = Job.objects.get(id=request.data['job_title'])
except:
new_job = request.data['new_job']
if new_job:
job_title = Job.objects.create(
name=new_job['name'],
min_salary=new_job['min_salary'],
max_salary=new_job['max_salary']
)
employee = Employee.objects.create(
user=c_user,
company=company,
department=department,
job_title=job_title,
hr_number=request.data['hr_number'],
identification_number=request.data['identification_number'],
tax_id_number=request.data['tax_id_number'],
joining_date=request.data['joining_date']
)
except Exception as e:
print(e)
return Response(status=status.HTTP_201_CREATED)
DRF do not manage nested serialiser or this kind of things. that said, you can simplify your code using Model.objects.get_or_create
example :
try:
job_title = Job.objects.get(id=request.data['job_title'])
except:
new_job = request.data['new_job']
if new_job:
job_title = Job.objects.create(
name=new_job['name'],
min_salary=new_job['min_salary'],
max_salary=new_job['max_salary']
)
# can be write with get_or_create:
job_defaults = {
'name': new_job['name'],
'min_salary': new_job['min_salary'],
'max_salary': new_job['max_salary']
}
Job.objects.get_or_create(name=new_job['name'],defaults=job_defaults)
You can also use Model Serializer to manage filtering + validating + save sub object
examples :
# serializers.py
class JobSerializer(serializers.ModelSerializer):
class Meta:
model = Job
fields = ('id', 'name', 'min_salary', 'max_salary')
# inside views.py's post method
try:
job_title = Job.objects.get(id=request.data['job_title'])
except:
JobSerializer(data=new_job).save()
see also:
DRF documentation about writable nested serializers
External extension to help with nested serializers