I'm new to tastypie and am trying to post to the following model:
class UserScore(models.Model):
"""
User Scores, used to categorise users
"""
user = models.OneToOneField(User)
category = models.ForeignKey(Category)
score = models.IntegerField()
and this is my api:
class UserScoreResource(ModelResource):
category = fields.ForeignKey(CategoryResource, 'category')
user = fields.OneToOneField(UserResource, 'user')
class Meta:
queryset = UserScore.objects.all()
resource_name = 'score'
authorization=Authorization()
allowed_methods = ['post', 'put', 'get']
In my unit tests I'm trying to run the following:
def test_no_post_to_userscore(self):
post_data = {
'user': {'resource_uri':'/api/v1/user/0/'},
'category': {'resource_uri':'/api/v1/category/0/'},
'score': 50,
}
print response.status_code
But am receiving a 500 error. Any help greatly appreciated!
Solved - bad syntax. Long day
Thanks
Related
I have to construct a comment API to show whether the current member has liked it.
In Django I can send users infomation by context in serializer.
I have no idea in FastApi.
class Level2CommentListSerializer(serializers.ModelSerializer):
favor = serializers.SerializerMethodField()
class Meta:
model = CommentRecord
fields = "__all__"
read_only_fields = ['id', 'user', 'article', 'content', 'root', 'replyId', 'depth']
def get_favor(self, obj):
uuid = self.context['user']
if not uuid:
return False
data = CommentFavorRecord.objects.filter(comment=obj, user__uuid=uuid)
if data:
return True
else:
return False
I tried:
client.lists.members.create('1111111', {
'email_address' : 'frogger116#gmail.com',
'status' : 'subscribed',
"tags": [{'name': 'frogger', 'status' : 'active'}],
})
and get:
mailchimp3.mailchimpclient.MailChimpError: {
'type': 'http://developer.mailchimp.com/documentation/mailchimp/guides/error-glossary/',
'title': 'Invalid Resource',
'status': 400,
'detail': 'Expected argument of type "string", "stdClass" given',
'instance': '5ae1b966-ed35-49f1-ad32-0a39c3d63593'
}
Without the "tags" line the satement works
This works for me (you need hash the email address)
hash = get_subscriber_hash(email_address)
data = {
'tags': [
{'name': 'foo', 'status': 'active'},
{'name': 'bar', 'status': 'inactive'}
]
}
client.lists.members.tags.update(list_id=list_id, subscriber_hash=hash, data=data)
I think in the latest version the tags are just strings. At least, this is what I use.
client.lists.members.create('1111111', {
'email_address' : 'frogger116#gmail.com',
'status' : 'subscribed',
'tags': ['frogger','invaders']
})
Also, to be certain, make sure tags exist in the system. Especially if doing an update rather than insert. Its not super consistant about dealing with unknown tags.
Also of course, make sure the '1111111' audience id exists.
SUPPLEMENTAL CODE
Of course, I've found a better way to ensure what you want is to use other methods to add to tags rather than on insert. You may have to tweak the below functions as they are part of a class, but they should give you other ideas on how to add and remove tags from users
def bulkUpdateTagNamesForEmails(self, emailsToUpdate, tagNames, remove=False):
audienceId = self.audienceId
if remove:
data = {'members_to_remove':emailsToUpdate}
else:
data = {'members_to_add':emailsToUpdate}
segments = None
for tagName in tagNames:
segId,segments = self.getSegmentIdFromTagName(tagName,segments)
if segId:
self.brResponse = self.mcClient.lists.segments.update_members(list_id=audienceId, segment_id=segId, data=data)
def createTagNameIfNeeded(self, tagName):
audienceId = self.audienceId
# Check for tag name
found = False
segments = self.mcClient.lists.segments.all(list_id=audienceId, get_all=False)
for segment in segments['segments']:
if segment['name'] == tagName:
found = True
print("Found tag")
# If not found, create it
if not found:
print(f"Creating new tag {tagName}")
data = {'name': tagName,'static_segment': []}
self.mcClient.lists.segments.create(list_id=audienceId, data=data)
def getSegmentIdFromTagName(self,reqTagName,segments=None):
audienceId = self.audienceId
reqId = None
if not segments:
segments = self.mcClient.lists.segments.all(list_id=audienceId, get_all=True)
for segment in segments['segments']:
segName = segment['name']
segId = segment['id']
if segName == reqTagName:
reqId = segId
break
return reqId,segments
I am creating a sandbox app as Api-platform practice and I have the following problem to address:
Let's consider following REST endpoint for user entity:
DISCLAIMER in the code examples there are a little more attributes but the whole concept applies regarding that
Collection-get(aka. /api/users) - only available for admin users(all attributes available, maybe we exclude hashed password)
POST - everyone should have access to following attributes: username, email, plainPassword(not persisted just in case someone asks)
PATCH/PUT - here it becomes quite tricky: I want those with ROLE_ADMIN to have access to username, email, plainPassword fields. And those who are the owners to only be able to alter plainPassword
DELETE - only ROLE_ADMIN and owners can delete
I will start with the resource config
resources:
App\Entity\User:
# attributes:
# normalization_context:
# groups: ['read', 'put', 'patch', 'post', 'get', 'collection:get']
# denormalization_context:
# groups: ['read', 'put', 'patch', 'post', 'get', 'collection:get']
collectionOperations:
get:
security: 'is_granted("ROLE_ADMIN")'
normalization_context: { groups: ['collection:get'] }
post:
normalization_context: { groups: ['admin:post', 'post'] }
itemOperations:
get:
normalization_context: { groups: ['admin:get', 'get'] }
security: 'is_granted("ROLE_ADMIN") or object == user'
put:
normalization_context: { groups: ['admin:put', 'put'] }
security: 'is_granted("ROLE_ADMIN") or object == user'
patch:
normalization_context: { groups: ['admin:patch', 'patch'] }
security: 'is_granted("ROLE_ADMIN") or object == user'
delete:
security: 'is_granted("ROLE_ADMIN") or object == user'
Here is the serializer config
App\Entity\User:
attributes:
username:
groups: ['post', 'admin:put', 'admin:patch', 'collection:get', 'get']
email:
groups: ['post', 'admin:put', 'admin:patch', 'collection:get', 'get']
firstName:
groups: ['post', 'admin:put', 'admin:patch', 'collection:get', 'get']
lastName:
groups: ['post', 'admin:put', 'admin:patch', 'collection:get', 'get']
plainPassword:
groups: ['post', patch]
createdAt:
groups: ['get', 'collection:get']
lastLoginDate:
groups: ['get', 'collection:get']
updatedAt:
groups: ['collection:get']
Here is the context group builder(Registered as service as it's stated in API-platform doc
<?php
namespace App\Serializer;
use Symfony\Component\HttpFoundation\Request;
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
final class AdminContextBuilder implements SerializerContextBuilderInterface
{
private $decorated;
private $authorizationChecker;
public function __construct(SerializerContextBuilderInterface $decorated, AuthorizationCheckerInterface $authorizationChecker)
{
$this->decorated = $decorated;
$this->authorizationChecker = $authorizationChecker;
}
public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array
{
$context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
if ($this->authorizationChecker->isGranted('ROLE_ADMIN')) {
switch($request->getMethod()) {
case 'GET':
$context['groups'][] = 'admin:get';
break;
case 'POST':
$context['groups'][] = 'admin:post';
case 'PUT':
$context['groups'][] = 'admin:put';
case 'PATCH':
$context['groups'][] = 'admin:patch';
}
}
return $context;
}
}
The issue is that even if I'm logged as a user with only ROLE_USER I am still able to alter username field which should be locked according to the admin:patch normalization group. I am pretty new to the api-platform and I can't quite understand why this does not work but I guess there will be an issue with the context builder. Thanks for your help I'll keep the question updated if I come up with something in the meantime
After investigating the docs and browsing youtube and most of all experimenting with the aformentioned user resource I came up with the solutionLet's start with the configuration again:
resources:
App\Entity\User:
collectionOperations:
get:
security: 'is_granted("ROLE_ADMIN")'
normalization_context: { groups: ['collection:get'] }
denormalization_context: { groups: ['collection:get'] }
post:
normalization_context: { groups: ['post'] }
denormalization_context: { groups: ['post'] }
itemOperations:
get:
normalization_context: { groups: ['get'] }
security: 'is_granted("ROLE_ADMIN") or object == user'
patch:
normalization_context: { groups: ['patch'] }
denormalization_context: { groups: ['patch'] }
security: 'is_granted("ROLE_ADMIN") or object == user'
delete:
security: 'is_granted("ROLE_ADMIN") or object == user'
The main difference between the starting point is that the admin actions should never be stated in the operation groups because they will be added by default to the context.
Next the property groups where we define all the operations available on certain property
App\Entity\User:
attributes:
id:
groups: ['get', 'collection:get']
username:
groups: ['post', 'admin:patch', 'get', 'collection:get']
email:
groups: ['post', 'admin:patch', 'get', 'collection:get']
plainPassword:
groups: ['post', 'patch', 'collection:get']
firstName:
groups: ['post', 'patch', 'get', 'collection:get']
lastName:
groups: ['post', 'get', 'collection:get']
createdAt:
groups: ['get', 'collection:get']
lastLoginDate:
groups: ['get', 'collection:get']
updatedAt:
groups: ['collection:get']
This is fairly the same from as in the question only thing we need to configure is which actions require to be the 'admin' this can be changed according to your needs whatever you program a blog, library, store or whatever and need some custom actions per role on your API.
At last is the custom context builder
<?php
namespace App\Serializer;
use Symfony\Component\HttpFoundation\Request;
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
final class AdminContextBuilder implements SerializerContextBuilderInterface
{
private $decorated;
private $authorizationChecker;
public function __construct(SerializerContextBuilderInterface $decorated, AuthorizationCheckerInterface $authorizationChecker)
{
$this->decorated = $decorated;
$this->authorizationChecker = $authorizationChecker;
}
public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array
{
$context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
if ($this->authorizationChecker->isGranted('ROLE_ADMIN')) {
$context['groups'][] = 'admin:patch';
$context['groups'][] = 'admin:post';
$context['groups'][] = 'admin:get';
}
return $context;
}
}
This is fairly simple and can be extended on your personal needs basically we check if the current user is an admin give him the properties from groups a, b, c etc. This can also be specified per entity(more on that you can find in API platform doc BookContextBuilder fairly simple
I am pretty sure this is the bread and butter anyone will ever need when building some simple or even complex API where roles will determine who can do what. If this answer will help you pleas be sure to up my answer thanks a lot and happy coding!
I'm reviewing some old code I wrote in Express/Mongo/Mongoose (based on an online tutorial) and can't locate the portion of code which dictates which Collection in MongoDB gets written to.
I have a Database, UsersDB and within that database there are several Collections. The Collection that keeps growing every time the code executes user.save is the users Collection. But I can't find any reference in my code base to the users collection.
The only place in my code where I save a User is:
var app = require('../app');
var util = require('util');
var User = require('../models/user'),
Auth = User.Auth,
Name = User.Name,
Email= User.Email,
Phone = User.Phone,
Address = User.Address,
Company = User.Company,
PersonalData = User.PersonalData,
Id = User.Id,
Photo = User.Photo,
Member = User.Member,
CreditCard = User.CreditCard,
UserObj = User.User;
var moment = require('moment');
var async = require('async');
. . .
. . .
exports.user_create_post = [
(req,res, next) => {
console.log("Request: " + util.inspect(req.body));
},
//VALIDATE
body('mainEmail', 'Must be valid email.').isLength({min: 5}).trim(),
//SANITIZE
sanitizeBody('*').escape(),
//POPULATE NEW DOCUMENT
(req,res,next) => {
const errors = validationResult(req);
var auth = new Auth(
dateEffective: {value: moment(Date.now()).format("YYYY-MM-DD hh:mm:ss SSS"), attr: {hidden: true, label: ""}},
username: {"value": req.body.username, "attr": {hidden: false, label: "Username: "}},
password: {"value": req.body.password, "attr": {hidden: false, label: "Password: "}},
mainEmail: {"value": req.body.mainEmail, "attr": {hidden: false, label: "Email: "}}
});
var user = new UserObj(
{authData: [auth]}
);
if (!errors.isEmpty()) {
const errorFormatter = ({ location, msg, param, value, nestedErrors }) => {
// Build your resulting errors however you want! String, object, whatever - it works!
return `${location}[${param}]: ${msg}`;
};
const result = validationResult(req).formatWith(errorFormatter);
if (!result.isEmpty()) {
return res.json({ errors: result.array() });
}
}
else {
user.save(function(err){
if (err) { return next(err);}
});
res.redirect("http://localhost:4200/two-fa/"+user._id);
}
}
I also have a Models module (user.js):
. . .
. . .
. . .
module.exports = {
Auth: mongoose.model('Auth', AuthSchema),
Name: mongoose.model('Name', NameSchema),
Email: mongoose.model('Email', EmailSchema),
Phone: mongoose.model('Phone', PhoneSchema),
Address: mongoose.model('Address', AddressSchema),
Company: mongoose.model('Company', CompanySchema),
PersonalData: mongoose.model('PersonalData', PersonalDataSchema),
Id: mongoose.model('Id', IdSchema),
Photo: mongoose.model('Photo', PhoneSchema),
Member: mongoose.model('Member', MemberSchema),
CreditCard: mongoose.model('CreditCard', CreditCardSchema),
User: mongoose.model('User', UserSchema)
}
I did a search on my entire code, and nowhere is there any mention of users, which is the Collection that's getting written to.
Where should I look to try to trace how the users collection is getting written to?
Thank you!
As per how-to-access-a-preexisting-collection-with-mongoose, if a 3rd argument to mongoose.model is not provided, then Mongo automatically "pluralizes" the model name to give the name of the Collection.
I have a form on a page and submit the details of the form on the click of the submit button and want to refresh the page after the object is added to the datastore. I read online that this could be because of the datastore's Eventual Consistency but can't seem to figure out a way to achieve the desired result(refreshing the page).
class AddUpdateProfile(webapp2.RequestHandler):
def post(self):
#This will be used to add/update profile in a datastore. Will be called when the user clicks on submit button on the Profile Page
template = JINJA_ENVIRONMENT.get_template('profile.html')
error = None
name = self.request.get('name')
designation = self.request.get('designation')
salary = self.request.get('salary')
currency = self.request.get('currency')
logging.info("Name = "+name)
logging.info("Designation = "+designation)
logging.info("Salary = "+salary)
logging.info("Currency = "+currency)
profile = UserProfile(parent=userProfile_key(users.get_current_user().email()))
profile.name = name
profile.designation = designation
profile.salary = int(salary)
profile.currency = currency
profile.email = str(users.get_current_user().email())
profile.put()
#Go back to main page. TODO : Change this to update
self.redirect('/profile')
class Profile(webapp2.RequestHandler):
def get(self):
logging.info("Inside Profile Page")
user = users.get_current_user()
if user:
profileInfo = getProfileInformation(user.email())
logging.info("Found a user inside Profile Page")
url = users.create_logout_url(self.request.uri)
if profileInfo is None or not profileInfo:
logging.info("Email = "+user.email())
logging.info("Profile Info not found")
template_values = {
'user': user.nickname(),
'url': url
}
else:
logging.info("Profile Info found")
template_values = {
'user': user.nickname(),
'url': url,
'name' : profileInfo[0].name,
'designation' : profileInfo[0].designation,
'salary' : profileInfo[0].salary,
'currency' : profileInfo[0].currency
}
template_values = template_values
template = JINJA_ENVIRONMENT.get_template('profile.html')
self.response.write(template.render(template_values))
else:
logging.info("User not found. Loading Landing page")
template_values = {
'url' : users.create_login_url(self.request.uri)
}
template = JINJA_ENVIRONMENT.get_template('landing.html')
self.response.write(template.render(template_values))
class MainPage(webapp2.RequestHandler):
def get(self):
logging.info("Inside MainPage")
user = users.get_current_user()
if user:
logging.info("Found a user inside MainPage")
url = users.create_logout_url(self.request.uri)
url_linktext = 'SIGN OUT'
template_values = {
'user': user.nickname(),
'url': url,
'userPage' : "no",
'url_linktext': url_linktext,
}
template = JINJA_ENVIRONMENT.get_template('index.html')
self.response.write(template.render(template_values))
else:
logging.info("User not found. Loading Landing page")
template_values = {
'url' : users.create_login_url(self.request.uri)
}
template = JINJA_ENVIRONMENT.get_template('landing.html')
self.response.write(template.render(template_values))
app = webapp2.WSGIApplication([
('/', MainPage),
('/profile', Profile),
('/addProfile', AddUpdateProfile)
], debug=True)
It would be great if someone could have a look at the code and give me some input on how to resolve the issue.
Any help is really appreciated!
Thanks!
Not sure if this is what you are looking for, but in general, if you want to refresh the page, you should do it using Javascript/JQuery on your page.
Have your endpoint send back a JSON response back to the '/profile'. The response should look something like:
{"success":"success"}
Or if, you need to send an error message:
{"error": "insert error message here"}
Your Javascript and/JQuery should then check if "error" is in the response. If it is throw an error message, otherwise, refresh the page.
How to reload a page using Javascript?