Create object if not exists in Django ImportExportModelAdmin - django

I have these two models:
Profile_model.py
class Profile(models.Model):
firstname = models.CharField(max_length=200, blank=False)
lastname = models.CharField(max_length=200, blank=False)
email = models.CharField(max_length=200, unique=True, blank=False)
...
Investment_model.py
class Investment(models.Model):
invested = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True)
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
...
and I have this admin:
Investment_admin.py
class InvestmentResource(resources.ModelResource):
...
firstname = fields.Field(attribute='profile',
widget=ForeignKeyWidget(Profile, field='firstname'),
column_name='firstname')
lastname = fields.Field(attribute='profile',
widget=ForeignKeyWidget(Profile, field='lastname'),
column_name='lastname')
email = fields.Field(attribute='email',
widget=ForeignKeyWidget(Profile, field='email'),
column_name='email')
class Meta:
model = Investment
fields = (
'firstname',
'lastname',
'email',
'invested',)
export_order = fields
class InvestmentAdmin(ImportExportModelAdmin, admin.ModelAdmin):
...
resource_class = InvestmentResource
...
I am using django's ImportExportModelAdmin for bulk imports and exports but when I try to import, I get this error:
I get that its producing this error because the profile hasn't been created yet. But what do I have to do to implement an update_or_create inside the ImportExportModelAdmin?

Option 1 is to use before_import() to scan through the dataset and create Profiles in batch if they do not exist already.
Option 2 is to override methods and create the profiles just before the Investment row is imported. This is only necessary for new Investment objects. This assumes that 'email' will uniquely identify a Profile, you will need to adjust this if not.
Note that firstname and lastname can be set on the Profile object before it is created.
class InvestmentResource(resources.ModelResource):
firstname = fields.Field(attribute='profile__firstname',
widget=CharWidget(), column_name='firstname')
lastname = fields.Field(attribute='profile__lastname',
widget=CharWidget(), column_name='lastname')
email = fields.Field(attribute='email',
widget=ForeignKeyWidget(Profile, field='email'),
column_name='email')
def before_import_row(self, row, row_number=None, **kwargs):
self.email = row["email"]
def after_import_instance(self, instance, new, row_number=None, **kwargs):
"""
Create any missing Profile entries prior to importing rows.
"""
if (
new
and not Profile.objects.filter(
name=self.email
).exists()
):
obj, created = Profile.objects.get_or_create(
name=self.email
)
if created:
logger.debug(f"no Profile in db with name='{self.email}' - created")
instance.profile = obj
Obviously the Profile creation will be a side-effect of an import, so you may need to consider using transactions if you don't want Profiles created if the import fails.

Related

Import-Export with "multilevel" models

I am figuring how can I manage this situation with django-import-export using the same excel and differents models with differents djangoapps. I have the following models:
# employees/models.py
class Employee(models.Model):
name = models.CharField(max_length=100)
job = models.ForeignKey(Job, on_delete=models.SET_NULL, null=True, blank=True)
# jobs/models.py
class Job(models.Model):
value = models.CharField(max_length=100)
department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True, blank=True)
place = models.ForeignKey(Place, on_delete=models.SET_NULL, null=True, blank=True)
class Department(models.Model):
value = models.CharField(max_length=100)
business = models.ForeignKey(Business, on_delete=models.SET_NULL, null=True, blank=True)
class Place(models.Model):
value = models.CharField(max_length=100)
business = models.ForeignKey(Business, on_delete=models.SET_NULL, null=True, blank=True)
class Business(models.Model):
name = models.CharField(max_length=100)
On excel I have following values:
xls_employee_name, xls_employee_job, xls_business_name
Jon Doe, Web Developer, Company 1
I know how to import employee name and his job because Job is directly related by ForeignKey. But how can I import business name?
Below is the code for employee name and his job:
# employees/resource.py
class EmpleadoResource(resources.ModelResource):
name = fields.Field(attribute='nombre', column_name='Nombre')
job = fields.Field(
column_name='xls_employee_job',
attribute='job',
widget=ForeignKeyWidget(
Job,
field='value'
))
class Meta:
model = Employee
fields = ('name','job',)
skip_unchanged = True
def before_import_row(self, row, **kwargs):
self.job = row["xls_employee_job"]
def after_import_instance(self, instance, new, row_number=None, **kwargs):
Job.objects.get_or_create(name=self.job)
Is it possible to import business name using one excel? I appreciate if someone could guide me. I'm also pretty new with django.
thank you for all your answers. I finally manage it and this is the solution(it was a little trickier for me, but very simple. I tried to translate all spanish names to english, sorry if I misslooked some):
# employees/resource.py
class EmployeeResource(resources.ModelResource):
name = fields.Field(attribute='name', column_name='Name')
job = fields.Field(
column_name='xls_employee_job',
attribute='job',
widget=ForeignKeyWidget(
Job,
field='value'
))
place = fields.Field(attribute='place', column_name='xls_place_column')
department = fields.Field(attribute='department', column_name='xls_department_column')
business = fields.Field(attribute='business', column_name='xls_business_name')
class Meta:
model = Employee
fields = ('name','job','place','department','business')
skip_unchanged = True
def before_import_row(self, row, **kwargs):
self.job = row["xls_employee_job"]
self.place = row["xls_place_column"]
self.department = row["xls_department_column"]
self.business = row["xls_business_name"]
def after_import_instance(self, instance, new, row_number=None, **kwargs):
Job.objects.get_or_create(name=self.job)
Centro.objects.get_or_create(name=self.centro)
Departamento.objects.get_or_create(name=self.departamento)
Empresa.objects.get_or_create(nombre=self.empresa)
I was stucked using widgets for business, department and place, but it was not necessary
Option 1
You can assign the column value for each row to a temporary attribute on the model (it doesn't matter that this attribute is not in the Employee model):
def before_import_row(self, row, **kwargs):
self.job = row["xls_employee_job"]
# assign row value to a temporary attr
self.business = row["xls_business_name"]
def after_import_instance(self, instance, new, row_number=None, **kwargs):
Job.objects.get_or_create(name=self.job)
# create instance using temp attr value
Business.objects.get_or_create(name=self.business)
Option 2
You can create all Business instances by processing the dataset in a batch before processing the Employee resources:
def before_import(self, dataset, using_transactions, dry_run, **kwargs):
for row in dataset.dict:
business = row["xls_business_name"]
Business.objects.create(name=business)
Note that you could use bulk_create() here to make this process more efficient.
Add transaction protection as you see fit, and bear in mind that the Business and Job entities will be created even if your import fails.

ValueError at /spotify/liked/: Cannot assign 'some value': "Liked_Songs.track_name" must be a "Profile" instance

So I have users whose profiles are automatically created after user creation with the help of Django Signals
models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
class Meta:
db_table = "Profile"
class Liked_Songs(models.Model):
track_name = models.ForeignKey(Profile , on_delete= models.CASCADE , related_name="track")
artiste_name= models.ForeignKey(Profile, on_delete= models.CASCADE , related_name="artiste")
album_name = models.ForeignKey(Profile, on_delete= models.CASCADE, related_name= "album")
class Meta:
db_table = "Liked Songs"
def __str__(self):
return self.track_name
In this Liked_Songs model, my views.py accesses an API and my aim is to allow all fields in that model to be populated with data from the API. So there will be multiple track_name etc received. So each Profile can have many track names etc. Is the ForeignKey appropriate for this?
However, when I use this route below, i get an error I have stated in the problem description.
Views.py
def liked(request):
try:
if "access_token" in request.session:
sp = Spotify(auth = request.session.get("access_token"))
liked = sp.current_user_saved_tracks(limit=30)['items']
for idx, item in enumerate(liked):
track = item['track']["name"]
artist= item["track"]["artists"][0]["name"]
album = item["track"]["album"]["name"]
Liked_Songs.objects.create(
track_name= track,
artiste_name= artist,
album_name = album
).save()
except SpotifyException:
return redirect(reverse("login"))
you are storing profile id in track so you cannot pass name in it so try to pass profile id in it
try this track_name_id = profile_id

Understanding how Models and Forms affect one another and database

I am creating form for users to input information too. I then want to save that information to a database and be able to extract information.
I am having difficulty however when I try to create a drop down list and multiple selection list. For example say I define a model as in my models.py file as such:
gi_category = models.CharField(max_length=4, choices=GI_NOGI)
Where I have GI_NOGI specified as
GI_NOGI = (('GI','GI'),('NOGI','NO-GI'))
My forms.py looks something like this
class RegisterForm(forms.ModelForm):
weightclass = forms.ChoiceField(choices=WEIGHTCLASS, required=True,label='Weight Class')
gi_category = forms.MultipleChoiceField(choices=GI_NOGI, label='Contest You Intend to Enter', widget=forms.CheckboxSelectMultiple(), required=True)
class Meta:
model = Register
fields = ['name', 'age', 'birthdate', 'address', 'city', 'state', 'zip',
'phone', 'email']
labels = {'name':'Name', 'age':'Age on April 21', 'birthdate':'Birth date',
'address':'Address', 'city':'City', 'state':'State', 'zip':'Zip',
'phone':'Phone', 'email':'Email'}
widgets = {
'birthdate':forms.TextInput(attrs={'placeholder': 'MM/DD/YYYY'}),
'phone':forms.TextInput(attrs={'placeholder': '(###)-###-####'})}
Now I believe I am overwriting the gi_category and weightclass from models somehow because I cant access their respective values in the database. I don't know how to create a SelectMultiple any other way than I had I did(If this is problem any insight would be great.
I am wondering what I am doing wrong?
Also on a related note I want to have a database value for gi_category in which it can either have values of 'NO-GI' or/and 'GI'
I am saving the form values using this views.py
WEIGHTCLASS = (
('MFYW','Men Fly Weight(129 lbs. & Under)'),
('MBW','Men Bantan Weight(130 lbs. to 139.9 lbs)'),
('MFEW','Men Feather Weight(140 lbs. to 149.9 lbs.)')
)
GI_NOGI = (('GI','GI'),
('NOGI','NO-GI')
)
class Register(models.Model):
"""register some text"""
name = models.CharField(max_length=200)
age = models.CharField(max_length=3)
email = models.EmailField(max_length=200)
birthdate = models.DateField()
address = models.CharField(max_length=200)
city = models.CharField(max_length=200)
state = models.CharField(max_length=200)
zip = models.CharField(max_length=5)
phone = models.CharField(max_length=14)
weightclass = models.CharField(max_length=4, choices=WEIGHTCLASS)
gi_category = models.CharField(max_length=4, choices=GI_NOGI)
def __str__(self):
"""Return a string represenation of the model."""
return self.weightclass
Thanks

Modelserializer using kwargs to get FK object

I'm creating a Django (1.8) webapp that saves racing laptimes and scoreboards. The database is populated using an API built using Django Rest Framework. It's the first time I'm trying to build a proper api using rest framework.
A quick overview of the models:
Event, A racing event/weekend
Session, A single race/practice/quali - FK Event
Car, A car taking part in a session - FK Session
Lap, Laps for specific car - FK Car
The Event is created manually, but the rest is supposed to be "dynamic" (get or create)
Right now I'm trying to create a new car using my API, but I'm stuck. To get the cars event and session I'm trying to use the url;
/api/results/skrotbilsracet-29042016/r1/cars/
The idea is to post data to this url and "get or create" a new car object.
To get the correct session object for the new car session FK, I need to use a custom function that takes the kwargs and tries to find the session.
The more I read about how to solve this, the more confused I get.
Could someone push me in the right direction?
This is my latest attempt at solving this, which just gives me "{"session":["This field is required."]}"
models.py
class Session(models.Model):
session_types = (
('p', 'Practice'),
('q', 'Qualification'),
('r', 'Race')
)
event_id = models.ForeignKey(Event, related_name='sessions')
name = models.CharField(max_length=2, blank=True)
current_session = models.BooleanField(default=True)
session_type = models.CharField(max_length=2,
choices=session_types)
started = models.DateTimeField(auto_now_add=True)
ended = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['started']
def save(self):
if not self.name:
# Get number of sessions
session_count = Session.objects.filter(event_id=self.event_id)\
.filter(session_type=self.session_type)\
.count()
session_count += 1
self.name = self.session_type + str(session_count)
super(Session, self).save()
def __unicode__(self):
string = self.started.strftime("%d-%m-%Y %H:%M") + ' - '
string += self.name.upper()
return(string)
class Car(models.Model):
session = models.ForeignKey(Session, related_name='cars')
number = models.IntegerField()
full_name = models.CharField(max_length=256, blank=True)
short_name = models.CharField(max_length=256, blank=True)
race_class = models.CharField(max_length=50, blank=True)
best_lap = models.IntegerField(blank=True, null=True)
best_lap_time = models.CharField(max_length=20, blank=True)
best_sector1 = models.CharField(max_length=20, blank=True)
best_sector2 = models.CharField(max_length=20, blank=True)
best_sector3 = models.CharField(max_length=20, blank=True)
best_speed = models.IntegerField(blank=True, null=True)
pitstops = models.IntegerField(blank=True, null=True)
total_time = models.CharField(max_length=20, blank=True)
transponder = models.CharField(max_length=50, blank=True)
apiUrls.py
urlpatterns = [
url(r'^raceslug/$', raceSlugView.as_view(), name='race-slug'),
url(r'^events/$', eventsView.as_view(), name='event-list'),
url(r'^session/$', getSessionView.as_view(), name='session-pk'),
url(r'^(?P<event_id>[a-z0-9\-]+)/$', eventView.as_view(), name='event-detail'),
url(r'^(?P<event_id>[a-z0-9\-]+)/(?P<name>[a-z0-9\-]+)/$', sessionView.as_view(), name='session-detail'),
url(r'^(?P<event_id>[a-z0-9\-]+)/(?P<name>[a-z0-9\-]+)/cars/$', carsView.as_view(), name='car-list'),
url(r'^(?P<event_id>[a-z0-9\-]+)/(?P<name>[a-z0-9\-]+)/(?P<number>[0-9]+)/$', carView.as_view(), name='car-detail'),
]
urlpatterns = format_suffix_patterns(urlpatterns)
api.py
class carsView(generics.ListCreateAPIView):
serializer_class = carSerializer
def get_session(self, event_id, name):
print('Getting session')
# Get event object
try:
event = Event.objects.get(event_id=event_id)
print('Found event')
except ObjectDoesNotExist:
print('Did not find event')
return
# Get session object
try:
session = event.sessions.get(name=name)
print('Found session: ', session)
return session
except ObjectDoesNotExist:
print('Did not find session')
return
def get_queryset(self):
print('Getting queryset')
print('event_id: ' + self.kwargs['event_id'])
print('name: ' + self.kwargs['name'])
session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
return(Car.objects.filter(session=session.pk))
def perform_create(self, serializer):
print('Creating new car')
session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
serializer.save(session=session)
serializers.py
class carSerializer(serializers.ModelSerializer):
laps = lapSerializer(many=True, read_only=True)
class Meta:
model = Car
fields = (
'session',
'number',
'full_name',
'short_name',
'race_class',
'best_lap',
'best_lap_time',
'best_sector1',
'best_sector2',
'best_sector3',
'best_speed',
'pitstops',
'total_time',
'transponder',
'laps')
Solution:
This is what I actually changed to get it working.
api.py
from rest_framework.serializers import ValidationError
class carsView(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
print('Creating new car')
session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
number = self.request.POST.get('number')
car = session.cars.filter(number=number)
if car.exists():
raise ValidationError('Car already exists')
serializer.save(session=session)
serializers.py
class carSerializer(serializers.ModelSerializer):
laps = lapSerializer(many=True, read_only=True)
session = serializers.StringRelatedField(required=False)
...
I see that you're creating your session ID there:
def get_queryset(self):
...
session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
return(Car.objects.filter(session=session.pk))
Then you don't need it in a serializer, only in a model. So you can set it a snot required in a serializer, but it will still be required in a model.
I guess this answer could help you: Django REST Framework serializer field required=false

Saving the Users Profile to the Model

When the form saves I want to save the object with a reference to the user's Profile page. So each profile can list every offer made by the user,
see Class based views query: get objects referenced by another model
ofertoj_oferto.profile_id may not be NULL
My model of "Oferto" contains a ForeignKey to the User's Profile. Right now I don't have an idea how i can tell the system "Get the logged in User's Profile and save this Ofeto with that ID"
Maybe there is some way where I lookup the profile associated with the user and don't need both user and profile on the Oferto model.
But I will still need to a list of every oferto made by the user on there profile
profiles.models
class Profile(BaseInfo):
bio = models.TextField(max_length=15000000)
user = models.ForeignKey(User)
views.py
class OfertoCreateView(LoginRequiredMixin, Turtle_CreateView):
model = Oferto
action = "created"
form_class = OfertoCreateForm
forms.py
class OfertoCreateForm(Turtle_Form):
class Meta:
model = Oferto
fields = ("name",
"description",
"tags",
"time",
"requirements",
"location",
"image",)
models.py
class Oferto(models.Model):
user = models.ForeignKey(User)
profile = models.ForeignKey(Profile)
name = models.CharField(max_length=150)
description = models.TextField(max_length=3000)
time = models.DecimalField(max_digits=10000000, decimal_places=2, null=True)
stelo = models.DecimalField(max_digits=10000000, decimal_places=2, null=True)
location = models.TextField(max_length=3000)
slug = AutoSlugField(('slug'), max_length=128, unique=True, populate_from=('name',))
tags = tagging.fields.TagField()
image = models.ImageField(upload_to='Ofertoj', blank=True, null=True)
requirements = models.TextField(max_length=550000, blank=True, null=True)
def get_absolute_url(self):
return reverse('oferto_detail', kwargs={'slug': self.slug})
def __unicode__(self):
return self.name
def get_tags(self):
return Tag.objects.get_for_object(self)
In your view/form/model save area: calling request.user returns a User object you can send to a model's ForeignKey field