Make (& test) 2 fields unique in Django Model - python

I have a model UserSite. Every User could have multiple sites. I now have to make them unique. So a site can't be added to a user if it already is appointed to that user. My Model code is:
class UserSite(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="sites")
site = models.ForeignKey(Site, on_delete=models.CASCADE, related_name="users")
class Meta:
unique_together = ("user", "site")
All fine. Now I want to make a test class who tests if this works. My test class:
from rest_framework.test import APITestCase
from models import UserSite
from factories import SiteFactory
from factories import UserFactory
class TestUniqueUserSite(APITestCase):
def setUp(self):
self.user = UserFactory()
self.test_site = SiteFactory()
self.test_site_2 = SiteFactory()
self.user_site = UserSite.objects.create(user=self.user, site=self.test_site)
def test_user_site_is_unique(self):
"""
Check if a new UserSite is unique
"""
self.user_site1 = UserSite.objects.create(user=self.user, site=self.test_site)
Factory:
class UserSiteFactory(factory.django.DjangoModelFactory):
class Meta:
model = UserSite
# Type hinting
def __new__(cls, *args, **kwargs) -> "UserSiteFactory.Meta.model":
return super().__new__(*args, **kwargs) # pragma: no cover
site = factory.SubFactory(SiteFactory)
user = factory.SubFactory(UserFactory)
user_role = factory.fuzzy.FuzzyChoice(UserSite.USER_ROLE_CHOICES)
This test gives no errors, the UserSite can be created. What do I wrong? The testing or the unique field? or both hehe, thanks!
When I run:
UserSite.objects.filter(user=self.user, site=self.test_site).all()
I got:
<QuerySet [<UserSite: pk: 1 - user_pk: 1 - site_pk: 1>, <UserSite: pk: 2 - user_pk: 1 - site_pk: 1>]>
So they are stored..

My guess is that the unique_constraint should be a list, not a tuple.

You are not setting the constraint correctly, it should be like
class UserSite(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="sites")
site = models.ForeignKey(Site, on_delete=models.CASCADE, related_name="users")
class Meta:
unique_together = (("user", "site"),)

Related

HyperlinkedRelatedFIeld and OneToOne relationship - object has no attribute

For my app I have offices and HR users (users with an OneToOneField to HRProfile), and I want to be able to assign HRs to offices. The issue I'm facing is that I just can't access the User's 'email' field when trying to look it up through a HyperlinkedRelatedField on an OfficeSerializer.
Relevant models:
class User(AbstractBaseUser, PermissionsMixin):
...
email = models.EmailField(unique=True)
...
from polymorphic.models import PolymorphicModel
class Profile(PolymorphicModel):
...
related_user = models.OneToOneField(settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
related_name="profile",
)
...
class HRProfile(Profile):
some_hr_specific_field = models.CharField()
def __str__(self) -> str:
return self.related_user.email
class Office(models.Model):
...
assigned_hrs = models.ManyToManyField(
"users.HRProfile", related_name="offices", blank=True
)
...
View:
class UserViewSet(
GenericViewSet,
):
serializer_class = UserSerializer
queryset = User.objects.all()
lookup_field = "email"
lookup_url_kwarg = "email"
lookup_value_regex = "[\\w#.]+"
...
# the OfficeViewSet just has the two required fields (queryset and serializer)
And the OfficeSerializer I'm having trouble with:
class OfficeSerializer(serializers.HyperlinkedModelSerializer):
...
assigned_hrs = serializers.HyperlinkedRelatedField(
queryset=HRProfile.objects.all(),
view_name="api:user-detail",
lookup_field="related_user.email",
many=True,
)
...
The above raises 'HRProfile' object has no attribute 'related_user.email' which I don't know what to make of, since the HRProfile.__str__ has no issues resolving the self.related_user.email path correctly for a given user.
I tried it with source='assigned_hrs.related_user', lookup_field='email' but to no avail. Also, I tried replacing the HyperlinkedRelatedField with an SlugRelatedField just to test, and it's the same issue with the slug_field="related_user.email".
I also tried using the double underscores instead of a period. What goes through however is lookup_field='some_hr_specific_field' which is surely not what I want, but it does resolve.
So I'm really running out of ideas, and any hints would be greatly appreciated!
Solved!
Define get_hrs on Office model:
class Office(models.Model):
def get_hrs(self):
hrs = self.assigned_hrs.all()
return [hr.related_user for hr in hrs]
And simply use the method as source:
class OfficeSerializer(serializers.HyperlinkedModelSerializer):
assigned_hrs = serializers.HyperlinkedRelatedField(
many=True,
queryset=User.objects.filter(groups__name="human_resource"),
source="get_hrs",
lookup_field="email",
view_name="api:user-detail",
)
# slugrelated works too :)
# assigned_hrs = serializers.SlugRelatedField(
# many=True,
# queryset=User.objects.filter(groups__name="human_resource"),
# source="get_hrs",
# slug_field="email",
# )

Django Models - How do you add subtype choice when user selects choice?

I'm working on a project where they have various job types that I've tackled with CHOICES, however, I want to add conditionals for WHEN job type 1 is chosen, SUBTYPES x-y become choices. I am having trouble with the syntax of how you would do that. I've included my pseudocode below... I appreciate any help!
from django.db import models
class User(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Job(models.Model):
name = models.CharField(max_length=255)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='jobs')
JOB_CHOICES = (
('carpentry', 'Carpentry'),
('cleaning', 'Cleaning'),
('electrician', 'Electrician'),
('handyman', 'Handyman'),
('hvac', 'HVAC'),
('painting', 'Painting'),
('pest', 'Pest'),
('plumbing', 'Plumbing'),
('roofing', 'Roofing'),
('property', 'Property'),
)
jobType = models.CharField(max_length=30, choices=JOB_CHOICES, default='handyman')
# If JobType = Carpentry:
# CARPENTRY_CHOICES = (
# ('trim', 'trim')
# ('sheetrock', 'Sheetrock')
# ('windows', 'Windows')
# ('doors', 'Doors')
# ('cabinets and shelving', 'Cabinets and Shelving')
# ('other', 'Other')
# )
# jobType = models.CharField(max_length=30, choices=CARPENTRY_CHOICES, default='other')
def __str__(self):
return self.name
Django Models
Django Serializer
/api editor
I would probably go with a job_type model, which has a name and a 'subtype' field.
class JobType(models.Model):
SubTypeChoices = (...)
name = models.CharField()
subtype = models.CharField(choices=SubTypeChoices, ...)
class Job(models.Model):
....
job_type = models.ForeignKey(JobType, ...)
....
This way you can associate your 'subtypes' with one job_type. And if for some reason you can have several job_types for a Job, use a ManyToMany field.

Django - Redirect to a subclass admin page

I'm creating a web application with Django.
In my models.py I have a class BaseProduct and a class DetailProduct, which extends BaseProduct.
In my admin.py I have BaseProductAdmin class and DetailProductAdmin class, which extends BaseProductAdmin.
I have another class called System, with a many to many relation with BaseProduct.
In the System admin page, I can visualize a list of the BaseProduct objects related to that system.
When I click on a product, the application redirect me to the BaseProduct admin page.
When a product of the list is a DetailProduct object, I would like to be redirected on the DetailProduct admin page instead.
Any idea on how to do this?
In models.py :
class BaseProduct(models.Model):
id = models.AutoField(primary_key=True, db_column='ID')
_prod_type_id = models.ForeignKey(
ProductTypes, verbose_name="product type", db_column='_prod_type_ID')
systems = models.ManyToManyField(
'database.System', through='database.SystemProduct')
def connected_to_system(self):
return self.systems.exists()
class Meta:
db_table = u'products'
verbose_name = "Product"
ordering = ['id', ]
class System(models.Model):
id = models.AutoField(primary_key=True, db_column='ID')
name = models.CharField(max_length=300)
def has_related_products(self):
""" Returns True if the system is connected with products. """
return self.products_set.exists()
class Meta:
managed = False
db_table = u'systems'
verbose_name = "System"
ordering = ['id', ]
class DetailProduct(BaseProduct):
options_id = models.AutoField(db_column='ID', primary_key=True)
product = models.OneToOneField(BaseProduct, db_column='_product_ID', parent_link=True)
min_height = models.FloatField(help_text="Minimum height in meters.")
max_height = models.FloatField(help_text="Maximum height in meters.")
def __init__(self, *args, **kwargs):
super(DetailProduct, self).__init__(*args, **kwargs)
if not self.pk:
self._prod_type_id = ProductTypes.objects.get(pk=9)
class Meta:
managed = False
db_table = 'detail_product'
verbose_name = "Detail product"
verbose_name_plural = "Detail products"
class SystemProduct(models.Model):
id = models.AutoField(primary_key=True, db_column='ID')
_system_id = models.ForeignKey(System, db_column='_system_ID')
_product_id = models.ForeignKey(BaseProduct, db_column='_Product_ID')
class Meta:
db_table = u'system_product'
unique_together = ('_system_id', '_product_id')
verbose_name = "system/product connection"
In my admin.py page:
class SystemProductInlineGeneric(admin.TabularInline):
model = SystemProduct
extra = 0
show_edit_link = True
show_url = True
class SystemProductForm(forms.ModelForm):
class Meta:
model = SystemProduct
fields = '__all__'
def __init__(self, *args, **kwargs):
""" Remove the blank option for the inlines. If the user wants to remove
the inline should use the proper delete button. In this way we can
safely check for orphan entries. """
super(SystemProductForm, self).__init__(*args, **kwargs)
modelchoicefields = [field for field_name, field in self.fields.iteritems() if
isinstance(field, forms.ModelChoiceField)]
for field in modelchoicefields:
field.empty_label = None
class SystemProductInlineForSystem(SystemProductInlineGeneric):
""" Custom inline, used under the System change page. Prevents all product-system
connections to be deleted from a product. """
form = SystemProductForm
raw_id_fields = ("_product_id",)
class SystemAdmin(admin.ModelAdmin):
inlines = [SystemProductInlineForSystem]
actions = None
list_display = ('id', 'name')
fieldsets = [('System information',
{'fields': (('id', 'name',), ),}),
]
list_display_links = ('id', 'configuration',)
readonly_fields = ('id',)
save_as = True
If I understand correctly, your question is how to change the InlineAdmin (SystemProductInlineForSystem) template so the "change link" redirects to the DetailProduct admin change form (instead of the BaseProduct admin change form) when the product is actually a DetailProduct.
I never had to deal with this use case so I can't provide a full-blown definitive answer, but basically you will have to override the inlineadmin template for SystemProductInlineForSystem and change the part of the code that generates this url.
I can't tell you exactly which change you will have to make (well, I probably could if I had a couple hours to spend on this but that's not the case so...), so you will have to analyze this part of the code and find out by yourself - unless of course someone more knowledgeable chimes in...

DRF : How to include serialized object that is returned from model method in serializer as a field?

DRF newbie here.
I'm trying to build an API for a school project. My project tree includes several applications and one is Profiles and the other is Trips.
profiles.models includes Driver and DriverSession Model.
trips.models includes Trip Model.
Below you can see the DriverSession Model I've created in profiles.models. I've added a model method 'get_active_trip' to be able to return the active trip of the driver -if it exists- to client application when it retrieves the corresponding DriverSession. If it does not exist I want trip field still included in the response with a value of None.
class DriverSession(models.Model):
driver = models.ForeignKey(Driver)
vehicle = models.ForeignKey(Vehicle)
start_timestamp = models.DateTimeField(auto_now_add=True)
end_timestamp = models.DateTimeField(null=True, blank=True)
class Meta:
verbose_name = 'DriverSession'
verbose_name_plural = 'Driver Sessions'
# Model method to get active trip of the driver
def get_active_trip(self):
try :
trip = Trip.objects.get(driver_session=self, Q(status='DI') | Q(status='OT'))
except Trip.DoesNotExist :
return None
else :
return trip
Here is the Serializer I've created for DriverSession model in profiles.models :
from trips.serializers import TripSerializer
class DriverSessionSerializer(serializers.ModelSerializer):
"""
Serializer class for DriverSession Model.
"""
driver = DriverSerializer(read_only=True)
driver_id = serializers.IntegerField(write_only=True)
vehicle = VehicleSerializer(read_only=True)
vehicle_id = serializers.IntegerField(write_only=True)
start_timestamp = serializers.DateTimeField(read_only=True,format='%Y-%m-%d %H:%M:%S')
end_timestamp = serializers.DateTimeField(required=False, allow_null=True, format='%Y-%m-%d %H:%M:%S')
trip = TripSerializer(source='get_active_trip', read_only=True)
class Meta:
model = DriverSession
fields = ('id',
'driver',
'driver_id',
'vehicle',
'vehicle_id',
'start_timestamp',
'end_timestamp',
'trip')
And this is partial view of my Trip Model in trips.models :
class Trip(models.Model):
rider = models.ForeignKey('profiles.Rider')
driver_session = models.ForeignKey('profiles.DriverSession', null=True, blank=True)
....
....
And finally, following is the partial view of the serializer class I've created for Trip Model in trips.serializers:
from profiles.serializers import DriverSessionSerializer
class TripSerializer(drf_serializers.ModelSerializer):
"""
Serializer class for Trip model
"""
....
....
driver_session = DriverSessionSerializer(read_only=True)
driver_session_id = drf_serializers.IntegerField(write_only=True, required=False, allow_null=True)
....
....
When I try to run the command 'runserver' I'm getting an import error in trips.serializers in which any of the profiles.serializers cannot be imported.
What am I missing here? How can I include serialized object that is returned from a model method in the corresponding serializer of the model as a field?
you want to add your custom field to your serilizer
you have to do some code like this :
class FooSerializer(serializers.ModelSerializer):
my_field = serializers.SerializerMethodField('is_named_bar')
def is_named_bar(self, foo):
return foo.name == "bar"
class Meta:
model = Foo
fields = ('id', 'name', 'my_field')
http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

How do I filter values in a Django form using ModelForm?

I am trying to use the ModelForm to add my data. It is working well, except that the ForeignKey dropdown list is showing all values and I only want it to display the values that a pertinent for the logged in user.
Here is my model for ExcludedDate, the record I want to add:
class ExcludedDate(models.Model):
date = models.DateTimeField()
reason = models.CharField(max_length=50)
user = models.ForeignKey(User)
category = models.ForeignKey(Category)
recurring = models.ForeignKey(RecurringExclusion)
def __unicode__(self):
return self.reason
Here is the model for the category, which is the table containing the relationship that I'd like to limit by user:
class Category(models.Model):
name = models.CharField(max_length=50)
user = models.ForeignKey(User, unique=False)
def __unicode__(self):
return self.name
And finally, the form code:
class ExcludedDateForm(ModelForm):
class Meta:
model = models.ExcludedDate
exclude = ('user', 'recurring',)
How do I get the form to display only the subset of categories where category.user equals the logged in user?
You can customize your form in init
class ExcludedDateForm(ModelForm):
class Meta:
model = models.ExcludedDate
exclude = ('user', 'recurring',)
def __init__(self, user=None, **kwargs):
super(ExcludedDateForm, self).__init__(**kwargs)
if user:
self.fields['category'].queryset = models.Category.objects.filter(user=user)
And in views, when constructing your form, besides the standard form params, you'll specify also the current user:
form = ExcludedDateForm(user=request.user)
Here example:
models.py
class someData(models.Model):
name = models.CharField(max_length=100,verbose_name="some value")
class testKey(models.Model):
name = models.CharField(max_length=100,verbose_name="some value")
tst = models.ForeignKey(someData)
class testForm(forms.ModelForm):
class Meta:
model = testKey
views.py
...
....
....
mform = testForm()
mform.fields["tst"] = models.forms.ModelMultipleChoiceField(queryset=someData.objects.filter(name__icontains="1"))
...
...
Or u can try something like this:
class testForm(forms.ModelForm):
class Meta:
model = testKey
def __init__(self,*args,**kwargs):
super (testForm,self ).__init__(*args,**kwargs)
self.fields['tst'].queryset = someData.objects.filter(name__icontains="1")
I know this is old; but its one of the first Google search results so I thought I would add how I found to do it.
class CustomModelFilter(forms.ModelChoiceField):
def label_from_instance(self, obj):
return "%s %s" % (obj.column1, obj.column2)
class CustomForm(ModelForm):
model_to_filter = CustomModelFilter(queryset=CustomModel.objects.filter(active=1))
class Meta:
model = CustomModel
fields = ['model_to_filter', 'field1', 'field2']
Where 'model_to_filter' is a ForiegnKey of the "CustomModel" model
Why I like this method:
in the "CustomModelFilter" you can also change the default way that the Model object is displayed in the ChoiceField that is created, as I've done above.
is the best answer:
BookDemoForm.base_fields['location'] = forms.ModelChoiceField(widget=forms.Select(attrs={'class': 'form-control select2'}),queryset=Location.objects.filter(location_for__fuel=True))

Categories