Putting a message in an inbox (Django models) - django

I have a couple of models and am a little confused about how to create all of the associations. I have profiles, events, messages, and inboxes. Each profile has an eventList that holds events. Each message is associated with an event too. Each inbox is associated with a profile and multiple messages. What I want to do is, whenever a message object is created, for it to be inserted into the inbox of every user who holds the event that message is associated with in their eventList. Providing my models and the view that I'm writing:
class Profile(models.Model):
user = models.OneToOneField(User)
name = models.CharField(max_length=50)
eventList = models.ManyToManyField(Event, blank="TRUE", null="TRUE", related_name='event_set+')
ownedEvent = models.ManyToManyField(Event, blank="TRUE", null="TRUE", related_name='owned_set')
def __unicode__(self):
return self.name
class inbox(models.Model):
def __unicode__(self):
return self.user.name
user = models.OneToOneField(Profile)
message = models.ManyToManyField(message, blank="TRUE", null="TRUE")
read = models.BooleanField(default = 0)
class message(models.Model):
def __unicode__(self):
return unicode(self.body)
def save(self, *args, **kwargs):
if not self.id:
self.created = datetime.datetime.today()
super(message, self).save(*args, **kwargs)
body = models.CharField(max_length=250)
eid = models.ForeignKey(Event)
#login_required
def sendMail(request):
event_id = request.POST['event_id']
e = Event.objects.get(id = event_id)
text = request.POST['body']
m = message(eid = e, body = text)
m.save()
users = e.eventList_set.all()
return HttpResponse(status = 200)

If you want this to always happen you can put the relevant code in the message.save() method or a post-save signal handler. If it's just for this view then you can put the code there. In either case, this should work:
# msg is the message instance
for box in inbox.objects.filter(user__eventList=msg.eid):
box.message.add(msg)

Related

Running test together fails, running test alone succeeds

I'm using pytest.
Going through the tests with a debugger, running both test_joining_previously_entered_queue_returns_previous_queue_details and test_joining_queue_enters_correct_position together, test_joining_previously_entered_queue_returns_previous_queue_details succeeds but test_joining_queue_enters_correct_position fails at queue = get_object_or_404(Queue, pk=kwargs["pk"]) in the JoinQueue view, where the view throws a 404
If I run test_joining_previously_entered_queue_returns_previous_queue_details individually, then the test will pass no problem.
What should happen is that create_company_one creates a Company object, which in turn creates a Queue object (shown in the overridden save method of Company). The associated Queue object does not seem to be created if test_joining_queue_enters_correct_position is run with the other test, but works if the test is run standalone
Why does this happen and how can I fix it?
test_join_queue.py
def create_company_one():
return Company.objects.create(name='kfc')
#pytest.mark.django_db
def test_joining_previously_entered_queue_returns_previous_queue_details(authenticated_client: APIClient):
create_company_one()
url = reverse('user-queue-join', args=[1])
first_response = authenticated_client.put(url)
first_data = first_response.data
second_response = authenticated_client.put(url)
second_data = second_response.data
assert first_data == second_data
#pytest.mark.django_db
def test_joining_queue_enters_correct_position(factory: APIRequestFactory):
"""
Makes sure that whenever a user joins a queue, their position is sorted correctly
based on WHEN they joined. Since all users in self.users join the queue
in a time canonical manner, we can iterate over them to test for their queue positions.
"""
create_company_one()
users = create_users()
view = JoinQueue.as_view()
url = reverse('user-queue-join',args=[1])
request = factory.put(url)
for queue_position, user in enumerate(users):
force_authenticate(request, user=user)
response = view(request, pk=1)
assert response.data["position"] == queue_position + 1
views.py
class JoinQueue(APIView):
permission_classes = [IsAuthenticated]
def put(self, request, *args, **kwargs):
# throws 404 here
queue = get_object_or_404(Queue, pk=kwargs["pk"])
user = request.user
try:
queue_details_of_a_user = user.queue_details.get(queue=queue)
except ObjectDoesNotExist:
queue_details_of_a_user = QueueDetails.objects.create(user=user, queue=queue)
serializer = QueueDetailsSerializer(queue_details_of_a_user)
return Response(serializer.data)
else:
serializer = QueueDetailsSerializer(queue_details_of_a_user)
return Response(serializer.data)
models.py
class Company(models.Model):
name = models.CharField(max_length=15, unique=True)
def save(self, *args: Any, **kwargs: Any) -> None:
created = bool(self.pk)
super().save(*args, **kwargs)
if not created:
Queue.objects.create(company=self)
logging.info(f"{self.name} has created its queue")
return None
def __str__(self) -> str:
return self.name
class Queue(models.Model):
company = models.OneToOneField(
Company, on_delete=models.CASCADE, related_name="queue"
)
users = models.ManyToManyField(User, through="QueueDetails", related_name="queues")
#property
def length(self) -> int:
return len(self.users.all())
#property
def sorted_users(self) -> "QuerySet[User]":
return self.users.all().order_by("queue_details__joined_at")
def __str__(self) -> str:
return f"{self.company}'s queue"
class QueueDetails(models.Model):
joined_at = models.DateTimeField(auto_now_add=True)
queue = models.ForeignKey(
Queue,
related_name="queue_details",
on_delete=models.CASCADE,
)
user = models.ForeignKey(
User,
related_name="queue_details",
on_delete=models.CASCADE,
)
#property
def position(self) -> int:
for index, user in enumerate(self.queue.sorted_users):
if user == self.user:
return index + 1
raise ValueError("User is not in the queue, Invalid queue position")

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.

Related model not updating in save override of a model being saved with ModelFormset

So I have Transaction model that is FK-d to a Share. In an 'Account' view, I have a ModelFormset of these Transactions and I can save multiple transactions by looping through the forms and saving them.
On my Transaction's save() method I try and update the balance on the linked Share. this works if I save one transaction, but when I POST my ModelFormset with multiple transactions, everytime I hit the self.share.balance = self.share.balance + amt line in the Transaction save() override (that is for every new Transaction), the share.balance is what it was before any of the previous transactions in the formset were saved.
Does anyone know why the added amount to share balance from a previous saved transaction is not carried on the subsequent saves (why only the last Transaction's amount will be added to share balance)?
Transaction model which should update balance on parent-model Share
class Transaction(models.Model):
share = models.ForeignKey(Share, on_delete=models.CASCADE, null=True, blank=True)
account = models.ForeignKey(Account, on_delete=models.CASCADE, null=True, blank=True)
db_cr = models.CharField(choices=DBCR, max_length=2)
amt = models.DecimalField('Amount', max_digits=11, decimal_places=2)
post_dt = models.DateTimeField('Post Time', null=True, blank=True)
def save(self, *args, **kwargs):
if not self.pk:
...
if self.share:
if self in self.share.transaction_set.all():
logging.error('Transaction %s already posted' % self.id)
return False
amt = self.amt if self.db_cr == 'cr' else -self.amt
self.share.balance = self.share.balance + amt
self.share.save()
Share Model
class Share(models.Model):
name = models.CharField(max_length=80)
account = models.ForeignKey(Account, on_delete=models.CASCADE)
definition = models.ForeignKey(ShareDef, on_delete=models.PROTECT)
balance = models.DecimalField('Balance', max_digits=11, decimal_places=2, default=0)
def __str__(self):
return '%s %s %s %s'%(self.account,
self.name,
self.definition.sym_code,
self.balance )
def save(self, *args, **kwargs):
if not self.pk:
if not self.name:
self.name = self.definition.name
super(Share, self).save(*args, **kwargs)
In view, I have a Transaction formset
#...in view
TranFormSet = modelformset_factory(Transaction, exclude=('origin','ach_entry'), extra=1)
if request.method=='POST':
...
tran_formset = TranFormSet(request.POST)
...
if tran_formset.is_valid():
for form in tran_formset:
tran = form.save(commit=False)
tran.account = account
tran.origin = 'tt'
tran.save()
else:
#...following kind of weird because of how I'm setting querysets of ModelChoiceFields
kwargs = {'account_instance': account}
tran_formset = TranFormSet(queryset=Transaction.objects.none())
tran_formset.form = (curry(TranForm, **kwargs))
Form
class TranForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
account_instance = kwargs.pop('account_instance', None)
super(TranForm, self).__init__(*args, **kwargs)
if account_instance:
self.fields['share'].queryset = account_instance.share_set.all()
if self.instance.pk:
del self.fields['share']
class Meta:
model=Transaction
exclude=['origin', 'ach_entry', 'account']
post_dt = forms.DateTimeField(initial=datetime.date.today(), widget=forms.TextInput(attrs=
{
'class': 'datepicker'
}))
share = forms.ModelChoiceField(empty_label='---------', required=False, queryset=Share.objects.all())
It's unclear what may be causing the issue, but it may be helpful to perform the update of the self.share.balance in a single update() query. This can be done using F expressions:
from django.db.models import F
class Transaction(models.Model):
# ...
def update_share_balance(self):
if self.db_cr == "cr":
amount = self.amt
else:
amount = -self.amt
# By using the queryset update() method, we can perform the
# change in a single query, without using a potentially old
# value from `self.share.balance`
return Share.objects.filter(id=self.share_id).update(
balance=F("balance") + amount
)
def save(self, *args, **kwargs):
if not self.pk:
# ...
if self.share:
# ...
self.update_share_balance()
# Also, be sure to call the super().save() method at the end!
super().save(*args, **kwargs)

How to count the number of subscribers

I'm using this model to allow user to subscribe or unsubscribe to a specific game. But now I'm unsure of how to count the number of users subscribed to a specific game.
class Subscription(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
game = models.ForeignKey(Game, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_active = models.BooleanField(default=True)
def __str__(self):
return str(self.id)
views.py:
class TitlePostListView(ListView):
model = Post
context_object_name = 'posts'
template_name = 'posts/game.html'
def get_queryset(self):
title = get_object_or_404(Game, slug=self.kwargs.get('slug'))
return Post.objects.filter(game=title).order_by('-date_published')
def get_context_data(self, **kwargs):
context = super(TitlePostListView, self).get_context_data(**kwargs)
context['game'] = get_object_or_404(Game, slug=self.kwargs.get('slug'))
context['subscription_status'] = subscription_status(self.request.user, context['game'])
return context
You would do something along the lines of this:
game = Game.objects.get(name="Some Game") # Gets the game obj (replace "name" with the identifier you use)
subscriptions = Subscription.objects.filter(game=game) # Filters all the subscriptions associated with the game obj above
sub_count = subscriptions.count() # Uses QuerySet method .count() to get amount of subscription instances, and avoids a potentially costly DB hit
EDIT
To get the query into your context when using ListView, you can do something like this:
class TitlePostListView(ListView):
model = Post
context_object_name = 'posts'
template_name = 'posts/game.html'
def get_queryset(self):
game = get_object_or_404(Game, slug=self.kwargs.get('slug'))
return Post.objects.filter(game=game).order_by('-date_published')
def get_context_data(self, **kwargs):
game = get_object_or_404(Game, slug=self.kwargs.get('slug'))
context = super(TitlePostListView, self).get_context_data(**kwargs)
context['game'] = game
context['subscription_status'] = subscription_status(self.request.user, context['game'])
context['sub_count'] = Subscription.objects.filter(game=game).count()
return context
And then in your template, you can use {{ sub_count }} to see the subscription count for that specified game.

Two fields related in 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)