I have the following flow for object creation:
A user wants to create an object on a webservice. In order to do it - I check if the user is eligible to pay a fee for it.
We make a request to know how much the user should pay. If 0 - user will see the regular Create button, if >0 he will see the PayPal button.
User fills in data and in the second case presses the PayPal button.
We make 2 consequential API requests:
Create an object with the parameter is_draft=True. Return its id.
Create an order object and use the id from the previous step.
Sometimes we cannot create an order, because the object doesn't exist in DB yet.
Django setting:
DATABASES["default"]["ATOMIC_REQUESTS"] = True
Django models:
class Object1(models.Model):
name = models.CharField()
...
class Object2(models.Model):
name = models.CharField()
...
class Order(models.Model):
user = models.ForeignKey(User)
amount = models.DecimalField()
#naive polymorphism
object1 = models.ForeignKey(Object1)
object2 = models.ForeignKey(Object2)
# An object will be created after PayPal's call to the webhook endpoint
class OrderPayment(models.Model):
...
order = models.ForeignKey(Order)
The problem is that when I try to create an order to want to assign object1 or object2 as FK it doesn't exist yet. Also I cannot call transaction.commit() after any object creation (Object1 or Object2) because I use atomic request and it leads to an error:
django.db.transaction.TransactionManagementError: This is forbidden when an 'atomic' block is active.
How can I ensure objects existence in DB?
:::::::::::::::::::::::::::::UPDATE:::::::::::::::::::::::::::::
I am sorry, I gave a lot of irrelevant info.
Basically the flow is:
Make an API request to create an Object1. Return its id.
Behind the the API is simple CreateAPIView from DRF.
Make an API request to create an Order with the ID from previous step.
In the second step, I try to create an object and create a relation with Object1 which might not exist sometimes.
UPDATE2
views.py
class CreateObject1APIView(generics.CreateAPIView):
serializer = Object1Serializer
class CreateOrderAPIView(generics.GenericAPIView):
serializer = OrderSerializer
def post(self, request, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
# Everything fails after calling save method, as Object1 sometimes doesn't exist.
serializer.save()
OrderService.call()
settings.py
DATABASES = {"default": env.db("DATABASE_URL")}
DATABASES["default"]["ATOMIC_REQUESTS"] = True
Related
I have tried to fix this issue for a while.
I have a model where there are logs entered by users and saved in Log Model. Also there is ActiveSession Model which has a ManytoMany relationship with Log Model.
In My views I am getting the POST and saving them in the Log Model. The next step is my issue where I am trying to add unqiue Logs inside the ActiveSession Model. By unique I mean by searching a field called 'log_order' in the Log Model.
The desired outcome is to:
1: Get the POST form and save the Log details in the Log Model
2: Search if the new saved Log has been previously added the many to many relation log under Active Session Model
3: If there is no Log in the ActiveSession Model to add the new Log in the Post to the activesession
4: If there are logs previously saved in ActiveSession to search for the log_order related to these logs
5: If there is no same log_order to add the new Log in the Post to the activesession
6: If the log_order is existing inside the activesession logs to delete this specific log and to add the new Log in the Post to the activesession
Models.py:
class Log(models.Model):
...................................
log_order = models.IntegerField(validators=[MinValueValidator(1)],blank=True, null=True)
date = models.DateTimeField(...........)
class LogForm(forms.Form):
.............................
log_order = forms.IntegerField()
class ActiveSession(models.Model):
log = models.ManyToManyField(Log)
..................................
Views.py:
def addlog(request, id):
active_session = ActiveSession.objects.get(id=ActiveSession.objects.last().id)
if request.method == 'POST':
form = LogForm(request.POST)
if form.is_valid():
data = Log()
data.log_order = request.POST.get('log_order')
data.log_repetitions = form.cleaned_data['log_repetitions']
data.log_weight = form.cleaned_data['log_weight']
data.save()
if active_session.log.values():
for i in active_session.log.values():
existing = i['log_order']
new = data.log_order
if int(existing) == int(new):
active_session.delete(active_session.log)
active_session.log.add(data)
active_session.save()
else:
active_session.log.add(data)
else:
active_session.log.add(data)
I have tried to save the data and update() but nothing is changing the dictionary the same exisiting outcome is printed and the new data is not replacing the existing ones. My objective is replace and avoid duplicating.
Update: I am also thought of if the keys is i['log_order'] is equal to data.log_order to delete the whole dictionary and add the new data whichever is easier
Trial:
With the below trial I got 'ManyRelatedManager' object is not subscriptable. I am not sure if I am going the wrong direction.
if active_session.log.values():
for i in range(len(active_session.log.values())):
new = data.log_order
if active_session.log[i]['log_order'] == new:
active_session.save()
else:
active_session.log.add(data)
else:
active_session.log.add(data)
Here is the current query:
<QuerySet [{'log_order': 1}, {'log_order': 1}]>
here is the requested query:
<QuerySet [{'log_order': 1}]>
I have tried and tested your code and found some issues with it.
Below, there is the code snippet I tested and it works.
I am not completely sure what is the purpose for the code, but I think you might find the method update_or_create useful (also works on related managers such as your log). Here is a link to Django docs.
def addlog(request, id):
if request.method == 'POST':
# Moved this part to the POST section of the view. It is sufficient to simplify
# the query in this way. Also, you probably want to check if there are sessions
# in the DB. Otherwise, the code would break. This returns a 404 in case there
# are absolutely no sessions.
active_session = ActiveSession.objects.last()
if active_session is None:
return Http404()
form = LogForm(request.POST)
if form.is_valid():
data = Log()
# Use form.cleaned_data instead of log_order. Form validates the data, POST dict doesn't.
data.log_order = form.cleaned_data['log_order']
# Ignored because it's missing from the model code
# data.log_repetitions = form.cleaned_data['log_repetitions']
# data.log_weight = form.cleaned_data['log_weight']
data.save()
# Use Django ORM querysets instead of values()
# See https://www.django-antipatterns.com/antipattern/over-use-of-values.html
active_session_logs = active_session.log.filter(log_order=data.log_order)
if active_session_logs.exists():
# Suggestion: try to use update() instead of a combination of delete & add.
# It reduces two SQL queries to one.
active_session_logs.delete()
active_session.log.add(data)
# After using RelatedManager.add(), no need to call save(), the changes
# are persisted in the DB simply by using add().
# Always redirect after successfully processing the form.
return redirect(reverse('test_73764999:add-log', args=(data.id,)))
else:
# If there are errors, re-render the same form with errors
return render(request, 'test_73764999/form.html', {
'form': form, 'id': id,
})
else:
# If this is a GET request, render an empty form
form = LogForm()
return render(request, 'test_73764999/form.html', {
'form': form, 'id': id,
})
I am building help desk system on django. Where anyone can open ticket for customer support.
Assume I have an parent object #001 and every child object of this parent have same ticket id. See the screenshot for better understand:
child1 and child2 have same ticket id like their parent object. How to apply bulk update on all objects if they have same ticket id?. Assume if I change ticket status of child2 then I want it will also apply bulk update of child1 and parent object. any idea how to do that on django?
here is my code:
models.py
class Contact(models.Model):
choice = (("pending","pending"),("solved","solved"),("closed","closed"))
ticket_status = models.CharField(choices=choice,max_length=100,default="pending")
parent =models.ForeignKey('self', on_delete=models.CASCADE,
null=True, blank=True, related_name='contact_parent')
sno = models.AutoField(primary_key=True,)
def save(self,*args,**kwargs):
if not self.parent and not self.support_ticket:
self.support_ticket= str(rand_support_ticket())
if not self.support_ticket:
self.support_ticket = self.parent.support_ticket
super(Contact,self).save(*args,**kwargs)
forms.py
class SupportAgentFrom(forms.ModelForm):
class Meta:
model = Contact
fields = ['support_message','ticket_status']
views.py
def AddReplySupport(request,slug):
# fetch the object related to passed id
obj = get_object_or_404(Contact, slug = slug)
# pass the object as instance in form
form = SupportAgentFrom(request.POST or None, instance = obj)
if form.is_valid():
form.instance.support_agent = request.user
form.save()
now I can update only single object once at a time. I want to apply bulk update on multiple objects at a time if they have same ticket id.
#Update1
Finally my problem is solved after following Dan Yishai solution. Here I want to try little bit explain his code so people can understand and solve this type of similar problems which I was facing.
Contact.objects.filter(
Q(support_ticket=form.instance.support_ticket)
).update( ticket_status="closed")
Above line of code searching and updating only those objects whose have exactly same ticket id.
You can update the item and all of it's children in a single query, just replace your code inside the if with something like:
count = Contact.objects.filter(
Q(pk=form.instance.pk) | Q(parent_id=form.instance.pk)
).update(support_agent=request.user)
You can use count to verify at least 1 object has been updated, and display to the user how many objects were modified.
Below I'm assuming sno is the Ticket Id
To grab the queryset:
queryset = Contact.objects.filter(sno=form.instance.sno)
Now you can use .update() or .bulk_update().
Update every object to have the same support agent:
queryset.update(support_agent=request.user)
Update every object to have a different support agent:
for contact in queryset:
contact.support_agent = value
queryset.bulk_update['support_agent']
As a new just learning flask, I have a few questions.
class Users(Resource):
decorators = [...,]
def get(self, user_id):
"""
GET /users : all uesers
GET /users/1 : user where user_id = 1
"""
# do query user by user_id
In real business, thers is rarely queried users based on only one dimension, instead, most of the time, we need to query users based on multiple dimensions, such as
query users by name
query users by email address
query users by name and create_time
More ...
My Questions:
Q1: as I know, there is only on get method in a flask-restful resource class, how to support so many query user functions in one get methon?
many be like this,
def get(user_id=None, user_email=None, user_create_time=None, user_name=None):
if user_id is None and user_email is None and user_create_time is None and user_name:
query_user_by_name
elif user_id is None and user_email and user_create_time is None and user_name is None:
query_user_by_email
...
OH...HO..., It's smelly and long, really a disaster!
Q2: I also think that use different Resource, like:
class UserNameResource(Resource):
def get(user_name):
query_user_by_name
class UserNameAndCreateTimeResource(Resource):
def get(user_name, create_time):
query_user_by_name_and create_time
...
It's also long, ugly, and doesn't conform to restful naming rules, this is my second question.
Q3: Use post methon instead of get, because restful is not restricted to use POST method to query a resource. If this is Ok, so here comes my third question: how to distinguish 'create a resource' from 'query a resource' in a post method? Maybe with my limited understanding, like this?
class Users(Resource):
def post(_method, **kwargs):
if _method == 'QUERY': # just a flag, could be others, as long as it's agreed in advance
# query_resource, then query user by diffenent conditions
if query_string in kwargs:
# query_sring is a dict, could used to ORM query,
# so there is only one method for query users resouce,
# there is no more other query_users_funcs like
# query_user_by_name, query_user_id,
# query_user_by_name_and_email ...
query_user_by_query_string
if _method == 'CREATE':
create_resource
I'm a newcomer to flask and restful. The above are my confusing, problems, and my own thoughts, which may be very immature.
Welcome to discuss and guide me. Thank you very much.
I was working my way through a couple of tutorials before xmas and I'm now trying to pick up where I left of.
Trying to teach myself REST by building some simple API end points. My confusion is coming from the fact that I cant find the tutorials I was using and there seems to be several different ways to solve the problem. So now I'm not sure what is the correct way to do it.
The code is working for returning all customers in the DB, now I want to return a specific customer based on their Id
Ok this is what I have...
I have an app.py that defines the resource like this:
api.add_resource(CustomerResource, '/Customer')
I have a models.py that defines the customer class like this:
ma = Marshmallow()
db = SQLAlchemy()
class Customer(db.Model):
__tablename__ = 'customers'
__table_args__ = {"schema":"business"}
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False)
status = db.Column(db.Integer, nullable=False)
class CustomerSchema(ma.Schema):
id = fields.Integer()
name = fields.String(required=True)
status = fields.Integer(required=True)
I have customer.py that defines the customer class as this:
customers_schema = CustomerSchema(many=True)
customer_schema = CustomerSchema()
class CustomerResource(Resource):
def get(self):
customers = Customer.query.all()
customers = customers_schema.dump(customers)
return {'status': 'success', 'data': customers}, 200
I have tried using request.args, but I dont believe that is the correct way to go due to the fact it will become unspported.
So the above all works with the GET successfully returning all customers. But now I want to be able to use GET http://127.0.0.1:5000/api/Customer/10 and to just return the details for customer id = 10
I'm not sure whether I need to define a new resource or whether the existing CustomerResource can be modified to test for the presence of a parameter.
Any guidance appreciated...
Yes you're correct don't use the request.args method, rather create another resource. Remember api.add_resource is essentially just mapping a handler to a RESTFUL endpoint. If you had a lot of duplicate business logic code shared between endpoints I would suggest that you abstract out that business logic into a helper function and utilize this helper function within your resource definitions, but in this particular case this isn't necessary. I would consider doing the following:
app.py:
api.add_resource(CustomerList, '/Customer')
api.add_resource(Customer, '/Customer/<int:id>')
I would rename customer.py to something like routes.py and it would contain the following:
class CustomerList(Resource):
def get(self):
customers = Customer.query.all()
customers = customers_schema.dump(customers)
return {'status': 'success', 'data': customers}, 200
class Customer(Resource):
def get(self, id):
customer = Customer.query.filter_by(id=id).first()
customer, errors = customers_schema.dump(customer)
if errors:
return jsonify(errors), 422
return customer, 200
Keep your models.py file as is, and I would consider utilizing the jsonify method that flask provides for returning your data within your RESTFUL endpoints. I have shown an example of this in the specific customer endpoint.
Hopefully that helps!
I'm building an API with the Django Rest Framework. The main requirement is that it should allow for the flexible inclusion of extra fields in the call. Based on a POST call, I would like to create a new record in Django, where some fields (varying in name and number) should be added to a JSON field (lead_request).
I doubt if I should use the ModelSerializer, as I don't know how to handle the various fields that should be merged into one field as a JSON. In the create method, I can't merge the additional fields into the JSON, as they aren't validated.
class Leads(models.Model):
campaign_id = models.ForeignKey(Campaigns, on_delete=models.DO_NOTHING)
lead_email = models.EmailField(null=True, blank=True)
lead_request = JSONField(default=dict, null=True, blank=True)
class LeadCreateSerializer(serializers.ModelSerializer):
def get_lead_request(self):
return {key: value for key, value in self.request.items() if key.startswith('rq_')}
class Meta:
model = Leads
fields = ['campaign_id',
'lead_email',
'lead_request']
def create(self, validated_data):
return Leads.objects.create(**validated_data)
The documentation mostly talks about assigning validated_data, but here that isn't possible.
If I understood correctly and you want to receive parameters through the URL as well, here's an example of how you could achieve what you want:
class LeadViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
data = request.data
lead_request = generate_lead_request(request)
data['lead_request'] = lead_request
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
...
And on generate_lead_request you could parse all the additional fields that may have been sent through request.data (body) as well as through the request.query_params.
If i understand the problem properly main obstruction here is we don't know the exact JSON data format of lead_request. I am thinking about two possible model of solution for this problem. I not sure either of them is appropriate or not. Just want to share my opinion.
case 1
Lets assume data passed to LeadCreateSerializer in this type of format
data = {
'campaign_id': campaign_id,
'lead_email': lead_email,
'lead_request': {
# lead_request
}
}
Then this is easy, normal model serializer should able to do that. If data is not in properly formatted and it possible to organize before passing to serializer that this should those view or functions responsibility to make it proper format.
case 2
Lets assume this is not possible to organize data before passing that in LeadCreateSerializer then we need to get our related value during the validation or get of lead_request. As this serializer responsibility is to create new instance and for that validate fields so we assume in self.context the whole self.context.request is present.
class LeadCreateSerializer(serializers.ModelSerializer):
def generate_lead_request(self, data):
# do your all possible validation and return
# in dict format
def get_lead_request(self):
request = self.context.request
lead_request = self.generate_lead_request(request.data)
return lead_request
class Meta:
model = Leads
fields = ['campaign_id',
'lead_email',
'lead_request']