Django: Filter many to many field - python

I expect to receive a var contact_exists that I can use to update some fields. However, the following query always gives me back django.core.exceptions.FieldError: Related Field got invalid lookup: event
Do you have any idea why event_related_fields__event doesn't work the way I expected?
for selected_order in Order.objects.all():
contact_exists = Contact.objects.filter(
event_related_fields__event=selected_order.event,
)
Here my models.py:
class Contact(TimeStampedModel):
consent = models.BooleanField(verbose_name=_("Consent"))
email = models.EmailField(verbose_name=_("Your email"))
first_name = models.CharField(
max_length=100, # TODO Length must be same as for billing model
verbose_name=_("First name"),
null=True,
blank=True,
)
last_name = models.CharField(
max_length=100, # TODO Length must be same as for billing model
verbose_name=_("Last name"),
null=True,
blank=True,
)
events = models.ManyToManyField(Event, related_name='contacts')
event_related_fields = models.ManyToManyField(
Event, related_name='event_related_fields', through='EventRelatedFields'
)
organizer = models.ForeignKey(
Organizer, on_delete=models.PROTECT, related_name='contacts'
) # PROTECT = don't allow to delete the organizer if contact exists
class Meta:
verbose_name = _("Contact")
verbose_name_plural = _("Contacts")
ordering = ('created',)
unique_together = ('email', 'organizer')
def __repr__(self):
return "{}: {}".format(self.__class__.__name__, self)
def __str__(self):
return self.email
class EventRelatedFields(TimeStampedModel):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
contact = models.ForeignKey(Contact, on_delete=models.CASCADE)
lead = models.BooleanField(
verbose_name='Lead', default=False
) # Contact who 'Signed Up'
attendee = models.BooleanField(
verbose_name='Attendee', default=False
) # Contact assigned to ticket
purchaser = models.BooleanField(
verbose_name='Purchaser', default=False
) # Contact made the order
class Meta:
unique_together = [['event', 'contact']]

You don't need the __event lookup, try using:
for selected_order in Order.objects.all():
contact_exists = Contact.objects.filter(
event_related_fields=selected_order.event,
)
The lookup part should contain field names of Event model.

Related

Update user profile with user uuid

I want to update user profile passing user uuid as kwarg.
Here is the url:
path("profile/update/<uuid:pk>", UpdateProfile.as_view(), name="update_profile"),
However, after I try to update my profile, it gives me an error.
Here is my view:
class UpdateProfile(LoginRequiredMixin, UpdateView):
model = Profile
user_type_fields = {
"Buyer": ["photo", "first_name", "last_name", "city"],
"Celler": ["photo", "name", "city", "address"],
}
def get(self, request, *args, **kwargs):
print(kwargs)
self.fields = self.user_type_fields[get_user_model().objects.get(pk=kwargs["pk"]).type]
return super().get(request, *args, **kwargs)
And here is the error itself:
Page not found (404)
No profile found matching the query
As I understand, django tries to find profile with uuid as in url, doesn't find it and returns me this error. However, if I change model in my view to user, it wouldn't be able to find fields as they belong to profile model. The only working option was to pass profile id as kwarg, but I don`t find it preferrable due to security reasons.
Could someone give me an advice on how to update profile with user uuid in kwargs?
Thanks in advance!
UPD:
Here are User and Profile models:
class CustomUser(AbstractBaseUser, PermissionsMixin):
class UserTypeChoices(models.TextChoices):
SINGLE_VOLUNTEER = "Single Volunteer", _("Single Volunteer")
VOLUNTEERS_ORGANISATION = "Volunteers Organisation", _("Volunteers Organisation")
CIVIL_PERSON = "Civil Person", _("Civil Person")
MILITARY_PERSON = "Military Person", _("Military Person")
type = models.CharField(
max_length=23,
choices=UserTypeChoices.choices,
)
uuid = models.UUIDField(
primary_key=True,
default=uuid4,
unique=True,
db_index=True,
editable=False,
)
email = models.EmailField(
_("email address"),
null=True,
blank=True,
)
phone = PhoneNumberField(
_("phone"),
null=True,
blank=True,
)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
is_active = models.BooleanField(
_("active"),
default=True,
help_text=_(
"Designates whether this user should be treated as active. " "Unselect this instead of deleting accounts."
),
)
def __str__(self):
if self.email:
return str(self.email)
else:
return str(self.phone)
USERNAME_FIELD = "email"
objects = CustomUserManager()
class Profile(models.Model):
user = models.OneToOneField(to="accounts.CustomUser", on_delete=models.CASCADE, blank=True, null=True)
photo = models.ImageField(upload_to="profile/", blank=True, null=True, default="profile/profile_default.png")
name = models.CharField(_("name"), max_length=150, blank=True, null=True, default=None)
first_name = models.CharField(_("first name"), max_length=150, blank=True, null=True, default=None)
last_name = models.CharField(_("last name"), max_length=150, blank=True, null=True, default=None)
city = models.CharField(_("city"), max_length=150, blank=True, null=True, default=None)
address = PlainLocationField()
def __str__(self):
if self.user.email:
return str(self.user.email)
else:
return str(self.user.phone)
Assuming the following model, where a user only has one profile:
class Profile(models.Model):
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
photo = models.ImageField()
# ... your other fields
You can then overwrite the get_object() method:
class UpdateProfile(LoginRequiredMixin, UpdateView):
model = Profile
fields = ['photo', '...']
def get_object(self):
user = get_user_model().objects.get(pk=self.kwargs['pk'])
profile = user.profile
return profile
And then use the UpdateView as normal.

Django: Accessing extra field in ManytoMany (through=)

I am trying to access the purchaser field in my ManytoMany field. I used through= to add some extra fields. However, it seems I am only able to access the event object, not the extra fields. Can someone explain to me why?
for selected_order in Order.objects.all():
contact_exists = Contact.objects.filter(
email=selected_order.email,
event_related_fields=selected_order.event,
)
if contact_exists:
contact = contact_exists.first()
for x in contact.event_related_fields.all():
print(x.purchaser)
models.py
class Contact(TimeStampedModel):
consent = models.BooleanField(verbose_name=_("Consent"))
email = models.EmailField(verbose_name=_("Your email"))
first_name = models.CharField(
max_length=100, # TODO Length must be same as for billing model
verbose_name=_("First name"),
null=True,
blank=True,
)
last_name = models.CharField(
max_length=100, # TODO Length must be same as for billing model
verbose_name=_("Last name"),
null=True,
blank=True,
)
events = models.ManyToManyField(Event, related_name='contacts')
event_related_fields = models.ManyToManyField(
Event, related_name='event_related_fields', through='EventRelatedFields'
)
organizer = models.ForeignKey(
Organizer, on_delete=models.PROTECT, related_name='contacts'
) # PROTECT = don't allow to delete the organizer if contact exists
class Meta:
verbose_name = _("Contact")
verbose_name_plural = _("Contacts")
ordering = ('created',)
unique_together = ('email', 'organizer')
def __repr__(self):
return "{}: {}".format(self.__class__.__name__, self)
def __str__(self):
return self.email
class EventRelatedFields(TimeStampedModel):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
contact = models.ForeignKey(Contact, on_delete=models.CASCADE)
lead = models.BooleanField(
verbose_name='Lead', default=False
) # Contact who 'Signed Up'
attendee = models.BooleanField(
verbose_name='Attendee', default=False
) # Contact assigned to ticket
purchaser = models.BooleanField(
verbose_name='Purchaser', default=False
) # Contact made the order
class Meta:
unique_together = [['event', 'contact']]
You are defining two many-to-many relationships for some reason, and you've called one of them event_related_fields, with the same related name. As a result you are confusing that with the through table. But since the through table is actually calledEventRelatedFields, you would access its related objects as eventrelatedfields_set.
You should only have one m2m, events:
events = models.ManyToManyField(Event, through='EventRelatedFields', related_name='contacts')
and your class EventRelatedFields should itself set related names:
event = models.ForeignKey(Event, related_name='event_related_fields', on_delete=models.CASCADE)
contact = models.ForeignKey(Contact, related_name='event_related_fields, on_delete=models.CASCADE)
Now you can do:
for x in contact.event_related_fields.all():
print(x.purchaser)

Django models: How to set up field value based on another field when new model item created in admin interface?

I have two related models: Plaint and WritOfExecutionTemplate. It is possible to add new template on plaint edit page using admin.TabularInline class. When user create new template from plaint page, I need to fill 2 power of attorney (POA) fields based on current plaint. User allowed to change POAs before template will be saved.
I tried to set callback method to field`s default attribute, but I have problem getting referenced plaint object.
class AbstractGeneratedCaseDocument(AbstractBaseModel):
class Meta:
abstract = True
case = models.ForeignKey(
verbose_name=_('case'),
to='Case',
on_delete=models.PROTECT,
)
power_of_attorney = models.ForeignKey(
verbose_name=_('power of attorney'),
to='ImportedDocument',
on_delete=models.PROTECT,
null=True,
blank=True,
related_name='+',
)
power_of_attorney_2 = models.ForeignKey(
verbose_name=_('power of attorney') + ' 2',
to='ImportedDocument',
on_delete=models.PROTECT,
null=True,
blank=True,
related_name='+',
)
class Plaint(AbstractGeneratedCaseDocument):
pass
class WritOfExecutionTemplate(AbstractBaseModel, WhoDidItMixin):
plaint = models.ForeignKey(
to='Plaint',
verbose_name=_('plaint'),
on_delete=models.PROTECT
)
def get_poa1_from_plaint(self):
if (self.plaint.power_of_attorney is not None
and self.plaint.case.plaintiff_representative == self.plaint.power_of_attorney.relative_person):
poa = self.plaint.power_of_attorney
else:
poa = None
return poa
def get_poa2_from_plaint(self):
# The same as for the first power of attorney
if (self.plaint.power_of_attorney_2 is not None
and self.plaint.case.plaintiff_representative == self.plaint.power_of_attorney_2.relative_person):
poa = self.plaint.power_of_attorney_2
else:
poa = None
return poa
power_of_attorney = models.ForeignKey(
verbose_name=_('power of attorney'),
to='ImportedDocument',
on_delete=models.PROTECT,
#null=True,
#blank=True,
related_name='+',
default=get_poa1_from_plaint,
)
power_of_attorney_2 = models.ForeignKey(
verbose_name=_('power of attorney') + ' 2',
to='ImportedDocument',
on_delete=models.PROTECT,
null=True,
blank=True,
related_name='+',
default=get_poa2_from_plaint
)
Admin model:
class WritOfExecutionTemplateAdminInline(admin.TabularInline):
model = WritOfExecutionTemplate
class PlaintAdmin(admin.ModelAdmin):
inlines = (
WritOfExecutionTemplateAdminInline,
)
Error message:
get_poa1_from_plaint() missing 1 required positional argument: 'self'
What is the right way to make what I need?

Django Inner Join on Derived Query

I have two models shown as follows. I want to be able to execute this query through the django ORM, essentially giving me the CustomUser class alongside two derived fields: max(message.sent_at) and max(case when read_at is null then 1 else 0 end). Those two fields would enable me to sort threads of messages by usernames and latest activity.
Here are my classes:
class CustomUser(AbstractBaseUser, PermissionsMixin):
username_validator = UnicodeUsernameValidator()
username = models.CharField(_('username'), max_length=150, unique=True, help_text=_('Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.'), validators=[username_validator], error_messages={'unique': _('A user with that username already exists.'),},)
email = models.EmailField(_('email address'), blank=True)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=150, blank=True)
is_staff = models.BooleanField(_('staff status'), default=False, help_text=_('Designates whether the user can log into this admin site.'),)
is_active = models.BooleanField(_('active'), default=True, help_text=_('Designates whether this user should be treated as active. Unselect this instead of deleting accounts.'),)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
bio = models.TextField(max_length=500, null=True, blank=True)
location = models.CharField(max_length=30, null=True, blank=True)
birth_date = models.DateField(null=True, blank=True)
phone_number = PhoneNumberField(default='+10000000000')
gender = models.CharField(max_length=32, choices=[(tag.name, tag.value) for tag in GenderChoice], default=GenderChoice.UNSPECIFIED.value)
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
ordering = ['username']
verbose_name = _('user')
verbose_name_plural = _('users')
and
class Message(AbstractIP):
subject = models.CharField(_('Subject'), max_length=120, blank=True)
body = models.TextField(_('Body')) # Do we want to cap length or enforce non-blank?
sender = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='sender_messages', verbose_name=_('Sender'), on_delete=models.CASCADE)
recipient = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='receiver_messages', verbose_name=_('Recipient'), blank=True, on_delete=models.CASCADE)
parent_msg = models.ForeignKey('self', related_name='next_messages', null=True, blank=True, verbose_name=_('Parent message'), on_delete=models.CASCADE)
sent_at = models.DateTimeField(_('sent at'), null=True, blank=True)
read_at = models.DateTimeField(_('read at'), null=True, blank=True)
replied_at = models.DateTimeField(_('replied at'), null=True, blank=True)
sender_deleted_at = models.DateTimeField(_('Sender deleted at'), null=True, blank=True)
recipient_deleted_at = models.DateTimeField(_('Recipient deleted at'), null=True, blank=True)
ip = models.GenericIPAddressField(verbose_name=_('IP'), null=True, blank=True)
user_agent = models.CharField(verbose_name=_('User Agent'), blank=True, max_length=255)
objects = MessageManager() # Manager for Message queries
def new(self):
"""Returns whether the recipient has read the message or not"""
if self.read_at is not None:
return False
return True
def replied(self):
"""Returns whether the recipient has written a reply to this message"""
if self.replied_at is not None:
return True
return False
def __str__(self):
if self.subject is not None:
return self.subject
if self.body is not None:
return self.body[:40]
return None
def get_absolute_url(self):
return reverse('messages_detail', args=[self.id])
def save(self, **kwargs):
if not self.id:
self.sent_at = timezone.now()
super(Message, self).save(**kwargs)
class Meta:
ordering = ['-sent_at']
verbose_name = _('Message')
verbose_name_plural = _('Messages')
The query I want to be able to perform equates to this, but I cannot figure out how to do it in the ORM, where %s is a placeholder for the CustomUser.id (pk) field of a given user.
SELECT webrtc_customuser.*
,MAX(webrtc_message.sent_at) AS sent_at
,MAX(CASE WHEN webrtc_message.read_at IS NULL AND webrtc_customuser.id <> webrtc_message.sender_id THEN 1 ELSE 0 END) AS has_unread
FROM webrtc_customuser
INNER JOIN webrtc_message
ON (
webrtc_customuser.id = webrtc_message.sender_id
AND webrtc_message.sender_id = %s
AND webrtc_message.sender_deleted_at IS NULL
) OR (
webrtc_customuser.id = webrtc_message.recipient_id
AND webrtc_message.recipient_id = %s
AND webrtc_message.recipient_deleted_at IS NULL
)
I managed to get the correct user_id and derived fields with the following queries but cannot figure out how to get the CustomUser properties joined alongside them.
messages = self.values(
user_fk=Case(When(sender=user, then='recipient'), default='sender', output_field=models.IntegerField())
).exclude(
sender=user, recipient=user
).filter(
Q(sender=user, sender_deleted_at__isnull=True) |
Q(recipient=user, recipient_deleted_at__isnull=True)
).annotate(
max_sent_at=Max('sent_at'),
has_unread=Max(Case(When(~Q(sender=user) & Q(read_at__isnull=True), then=1), default=0, output_field=models.IntegerField()))
).order_by()
Thank you in advance for your time!
Edit: updated ORM query
You need to specify the desired user properties individually:
messages = self.values(
user_email=Case(When(sender=user, then='recipient__email'), default='sender__email'),
user_username=Case(When(sender=user, then='recipient__username'), default='sender__username'),
)
Not very pretty, particularly as you have to repeat the CASE statement for every column and may even need to specify an output_field for every one.
To get around that, ie. to get all user properties without selecting them one by one, you'd either need to a) select from CustomUser.object (figuring out how to select the relevant users and get the relevant annotations), or b) select full message objects rather than just a values() dictionary. Then you can access the full user objects via message.senderand message.recipient. But here again, the challenge would be how to filter and annotate the messages queryset using subqueries, since just omitting values() will bust the aggregates in your annotations as every message object will then be unique.

How to expose some specific fields of model_b based on a field of model_a?

I want to create a ModelForm which gonna show some specific field of ControlInstruction if device_type of Device is equals DC. Otherwise show all fields.
Suppose,
if device type == 'DC':
show these filed in form-> on_off_flag, speed_flag, direction_flag
else:
show all
How can I do that?
class Device(models.Model):
DEVICE_TYPES = (
('AC', 'AC MOTOR'),
('DC', 'DC MOTOR'),
)
user = models.ForeignKey(User, on_delete=models.CASCADE)
device_id = models.CharField(max_length=64, unique=True, blank=False)
device_name = models.CharField(max_length=100, blank=False)
device_model = models.CharField(max_length=10)
device_type = models.CharField(max_length=2, choices=DEVICE_TYPES, blank=False)
location = models.CharField(max_length=150)
def __str__(self):
return self.device_name
class ControlInstruction(models.Model):
DIRECTION_CHOICES = (
('FW', 'Forward'),
('BW', 'Backward'),
)
# OneToOneField is is similar to a ForeignKey with unique=True, but the “reverse”
# side of the relation will directly return a single object.
device = models.OneToOneField(Device, on_delete=models.CASCADE, primary_key=True)
on_off_flag = models.BooleanField(default=False)
voltage_flag = models.FloatField(max_length=20, default=0)
current_flag = models.FloatField(max_length=20, default=0)
speed_flag = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(100)])
direction_flag = models.CharField(max_length=2, choices=DIRECTION_CHOICES, default='FW')
frequency_flag = models.IntegerField(default=0)
I would recommend creating two forms, one including only the fields for a DC device, and one form with all of the fields. Then in your view, choose which form to use based on the device_type.
class DeviceForm(forms.ModelForm):
class Meta:
model = Device
fields = "__all__"
def __init__(self,*args,**kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
if self.instance.device_type != "DC":
del self.fields["on_off_flag"]
del self.fields["speed_flag"]
del self.fields["direction_flag"]
But I dont recommended since you will find that this approach is very limited

Categories