mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-04 21:58:54 -05:00
settings wip
This commit is contained in:
@@ -62,7 +62,7 @@ class UserPreferenceAdmin(admin.ModelAdmin):
|
||||
|
||||
@staticmethod
|
||||
def name(obj):
|
||||
return obj.user.get_user_name()
|
||||
return obj.user.get_user_display_name()
|
||||
|
||||
|
||||
admin.site.register(UserPreference, UserPreferenceAdmin)
|
||||
@@ -75,7 +75,7 @@ class SearchPreferenceAdmin(admin.ModelAdmin):
|
||||
|
||||
@staticmethod
|
||||
def name(obj):
|
||||
return obj.user.get_user_name()
|
||||
return obj.user.get_user_display_name()
|
||||
|
||||
|
||||
admin.site.register(SearchPreference, SearchPreferenceAdmin)
|
||||
@@ -177,7 +177,7 @@ class RecipeAdmin(admin.ModelAdmin):
|
||||
|
||||
@staticmethod
|
||||
def created_by(obj):
|
||||
return obj.created_by.get_user_name()
|
||||
return obj.created_by.get_user_display_name()
|
||||
|
||||
if settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql']:
|
||||
actions = [rebuild_index]
|
||||
@@ -216,7 +216,7 @@ class CommentAdmin(admin.ModelAdmin):
|
||||
|
||||
@staticmethod
|
||||
def name(obj):
|
||||
return obj.created_by.get_user_name()
|
||||
return obj.created_by.get_user_display_name()
|
||||
|
||||
|
||||
admin.site.register(Comment, CommentAdmin)
|
||||
@@ -235,7 +235,7 @@ class RecipeBookAdmin(admin.ModelAdmin):
|
||||
|
||||
@staticmethod
|
||||
def user_name(obj):
|
||||
return obj.created_by.get_user_name()
|
||||
return obj.created_by.get_user_display_name()
|
||||
|
||||
|
||||
admin.site.register(RecipeBook, RecipeBookAdmin)
|
||||
@@ -253,7 +253,7 @@ class MealPlanAdmin(admin.ModelAdmin):
|
||||
|
||||
@staticmethod
|
||||
def user(obj):
|
||||
return obj.created_by.get_user_name()
|
||||
return obj.created_by.get_user_display_name()
|
||||
|
||||
|
||||
admin.site.register(MealPlan, MealPlanAdmin)
|
||||
|
||||
@@ -320,6 +320,24 @@ class CustomRecipePermission(permissions.BasePermission):
|
||||
return has_group_permission(request.user, ['guest']) and obj.space == request.space
|
||||
|
||||
|
||||
class CustomUserPermission(permissions.BasePermission):
|
||||
"""
|
||||
Custom permission class for user api endpoint
|
||||
"""
|
||||
message = _('You do not have the required permissions to view this page!')
|
||||
|
||||
def has_permission(self, request, view): # a space filtered user list is visible for everyone
|
||||
return has_group_permission(request.user, ['guest'])
|
||||
|
||||
def has_object_permission(self, request, view, obj): # object write permissions are only available for user
|
||||
if request.method in SAFE_METHODS and 'pk' in view.kwargs and has_group_permission(request.user, ['guest']) and request.space in obj.userspace_set.all():
|
||||
return True
|
||||
elif request.user == obj:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def above_space_limit(space): # TODO add file storage limit
|
||||
"""
|
||||
Test if the space has reached any limit (e.g. max recipes, users, ..)
|
||||
|
||||
@@ -43,7 +43,7 @@ class Integration:
|
||||
self.export_type = export_type
|
||||
self.ignored_recipes = []
|
||||
|
||||
description = f'Imported by {request.user.get_user_name()} at {date_format(datetime.datetime.now(), "DATETIME_FORMAT")}. Type: {export_type}'
|
||||
description = f'Imported by {request.user.get_user_display_name()} at {date_format(datetime.datetime.now(), "DATETIME_FORMAT")}. Type: {export_type}'
|
||||
icon = '📥'
|
||||
|
||||
try:
|
||||
|
||||
@@ -26,7 +26,7 @@ from recipes.settings import (COMMENT_PREF_DEFAULT, FRACTION_PREF_DEFAULT, KJ_PR
|
||||
SORT_TREE_BY_NAME, STICKY_NAV_PREF_DEFAULT)
|
||||
|
||||
|
||||
def get_user_name(self):
|
||||
def get_user_display_name(self):
|
||||
if not (name := f"{self.first_name} {self.last_name}") == " ":
|
||||
return name
|
||||
else:
|
||||
@@ -58,7 +58,7 @@ def get_shopping_share(self):
|
||||
]))
|
||||
|
||||
|
||||
auth.models.User.add_to_class('get_user_name', get_user_name)
|
||||
auth.models.User.add_to_class('get_user_display_name', get_user_display_name)
|
||||
auth.models.User.add_to_class('get_shopping_share', get_shopping_share)
|
||||
auth.models.User.add_to_class('get_active_space', get_active_space)
|
||||
|
||||
|
||||
@@ -133,16 +133,17 @@ class SpaceFilterSerializer(serializers.ListSerializer):
|
||||
return super().to_representation(data)
|
||||
|
||||
|
||||
class UserNameSerializer(WritableNestedModelSerializer):
|
||||
username = serializers.SerializerMethodField('get_user_label')
|
||||
class UserSerializer(WritableNestedModelSerializer):
|
||||
display_name = serializers.SerializerMethodField('get_user_label')
|
||||
|
||||
def get_user_label(self, obj):
|
||||
return obj.get_user_name()
|
||||
return obj.get_user_display_name()
|
||||
|
||||
class Meta:
|
||||
list_serializer_class = SpaceFilterSerializer
|
||||
model = User
|
||||
fields = ('id', 'username')
|
||||
fields = ('id', 'username', 'first_name', 'last_name', 'display_name')
|
||||
read_only_fields = ('username', )
|
||||
|
||||
|
||||
class GroupSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
||||
@@ -279,7 +280,7 @@ class SpaceSerializer(WritableNestedModelSerializer):
|
||||
|
||||
|
||||
class UserSpaceSerializer(WritableNestedModelSerializer):
|
||||
user = UserNameSerializer(read_only=True)
|
||||
user = UserSerializer(read_only=True)
|
||||
groups = GroupSerializer(many=True)
|
||||
|
||||
def validate(self, data):
|
||||
@@ -317,8 +318,8 @@ class MealTypeSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
|
||||
|
||||
class UserPreferenceSerializer(WritableNestedModelSerializer):
|
||||
food_inherit_default = serializers.SerializerMethodField('get_food_inherit_defaults')
|
||||
plan_share = UserNameSerializer(many=True, allow_null=True, required=False)
|
||||
shopping_share = UserNameSerializer(many=True, allow_null=True, required=False)
|
||||
plan_share = UserSerializer(many=True, allow_null=True, required=False)
|
||||
shopping_share = UserSerializer(many=True, allow_null=True, required=False)
|
||||
food_children_exist = serializers.SerializerMethodField('get_food_children_exist')
|
||||
image = UserFileViewSerializer(required=False, allow_null=True, many=False)
|
||||
|
||||
@@ -737,7 +738,7 @@ class RecipeSerializer(RecipeBaseSerializer):
|
||||
keywords = KeywordSerializer(many=True)
|
||||
rating = serializers.SerializerMethodField('get_recipe_rating')
|
||||
last_cooked = serializers.SerializerMethodField('get_recipe_last_cooked')
|
||||
shared = UserNameSerializer(many=True, required=False)
|
||||
shared = UserSerializer(many=True, required=False)
|
||||
|
||||
class Meta:
|
||||
model = Recipe
|
||||
@@ -783,7 +784,7 @@ class CommentSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
class CustomFilterSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
|
||||
shared = UserNameSerializer(many=True, required=False)
|
||||
shared = UserSerializer(many=True, required=False)
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['created_by'] = self.context['request'].user
|
||||
@@ -796,7 +797,7 @@ class CustomFilterSerializer(SpacedModelSerializer, WritableNestedModelSerialize
|
||||
|
||||
|
||||
class RecipeBookSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
|
||||
shared = UserNameSerializer(many=True)
|
||||
shared = UserSerializer(many=True)
|
||||
filter = CustomFilterSerializer(allow_null=True, required=False)
|
||||
|
||||
def create(self, validated_data):
|
||||
@@ -840,7 +841,7 @@ class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
|
||||
meal_type_name = serializers.ReadOnlyField(source='meal_type.name') # TODO deprecate once old meal plan was removed
|
||||
note_markdown = serializers.SerializerMethodField('get_note_markdown')
|
||||
servings = CustomDecimalField()
|
||||
shared = UserNameSerializer(many=True, required=False, allow_null=True)
|
||||
shared = UserSerializer(many=True, required=False, allow_null=True)
|
||||
shopping = serializers.SerializerMethodField('in_shopping')
|
||||
|
||||
def get_note_markdown(self, obj):
|
||||
@@ -904,7 +905,7 @@ class ShoppingListEntrySerializer(WritableNestedModelSerializer):
|
||||
ingredient_note = serializers.ReadOnlyField(source='ingredient.note')
|
||||
recipe_mealplan = ShoppingListRecipeSerializer(source='list_recipe', read_only=True)
|
||||
amount = CustomDecimalField()
|
||||
created_by = UserNameSerializer(read_only=True)
|
||||
created_by = UserSerializer(read_only=True)
|
||||
completed_at = serializers.DateTimeField(allow_null=True, required=False)
|
||||
|
||||
def get_fields(self, *args, **kwargs):
|
||||
@@ -972,7 +973,7 @@ class ShoppingListEntryCheckedSerializer(serializers.ModelSerializer):
|
||||
class ShoppingListSerializer(WritableNestedModelSerializer):
|
||||
recipes = ShoppingListRecipeSerializer(many=True, allow_null=True)
|
||||
entries = ShoppingListEntrySerializer(many=True, allow_null=True)
|
||||
shared = UserNameSerializer(many=True)
|
||||
shared = UserSerializer(many=True)
|
||||
supermarket = SupermarketSerializer(allow_null=True)
|
||||
|
||||
def create(self, validated_data):
|
||||
@@ -1085,7 +1086,7 @@ class InviteLinkSerializer(WritableNestedModelSerializer):
|
||||
if obj.email:
|
||||
try:
|
||||
if InviteLink.objects.filter(space=self.context['request'].space, created_at__gte=datetime.now() - timedelta(hours=4)).count() < 20:
|
||||
message = _('Hello') + '!\n\n' + _('You have been invited by ') + escape(self.context['request'].user.username)
|
||||
message = _('Hello') + '!\n\n' + _('You have been invited by ') + escape(self.context['request'].user.get_user_display_name())
|
||||
message += _(' to join their Tandoor Recipes space ') + escape(self.context['request'].space.name) + '.\n\n'
|
||||
message += _('Click the following link to activate your account: ') + self.context['request'].build_absolute_uri(reverse('view_invite', args=[str(obj.uuid)])) + '\n\n'
|
||||
message += _('If the link does not work use the following code to manually join the space: ') + str(obj.uuid) + '\n\n'
|
||||
|
||||
@@ -285,7 +285,7 @@
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'view_space,view_settings,view_history,view_system,docs_markdown' %}active{% endif %}">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false"><i
|
||||
class="fas fa-fw fa-user-alt"></i> {{ user.get_user_name }}
|
||||
class="fas fa-fw fa-user-alt"></i> {{ user.get_user_display_name }}
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownMenuLink">
|
||||
|
||||
@@ -7,22 +7,11 @@ from django.urls import reverse
|
||||
|
||||
from cookbook.models import UserSpace
|
||||
|
||||
LIST_URL = 'api:username-list'
|
||||
DETAIL_URL = 'api:username-detail'
|
||||
LIST_URL = 'api:user-list'
|
||||
DETAIL_URL = 'api:user-detail'
|
||||
|
||||
|
||||
def test_forbidden_methods(u1_s1):
|
||||
r = u1_s1.post(
|
||||
reverse(LIST_URL))
|
||||
assert r.status_code == 405
|
||||
|
||||
r = u1_s1.put(
|
||||
reverse(
|
||||
DETAIL_URL,
|
||||
args=[auth.get_user(u1_s1).pk])
|
||||
)
|
||||
assert r.status_code == 405
|
||||
|
||||
r = u1_s1.delete(
|
||||
reverse(
|
||||
DETAIL_URL,
|
||||
@@ -69,3 +58,56 @@ def test_list_space(u1_s1, u2_s1, u1_s2, space_2):
|
||||
|
||||
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 1
|
||||
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arg", [
|
||||
['a_u', 403],
|
||||
['g1_s1', 403],
|
||||
['u1_s1', 200],
|
||||
['a1_s1', 403],
|
||||
['g1_s2', 404],
|
||||
['u1_s2', 404],
|
||||
['a1_s2', 404],
|
||||
])
|
||||
def test_user_retrieve(arg, request, u1_s1):
|
||||
c = request.getfixturevalue(arg[0])
|
||||
|
||||
r = c.get(reverse(DETAIL_URL, args={auth.get_user(u1_s1).id}), )
|
||||
print(r.content, auth.get_user(u1_s1).username)
|
||||
assert r.status_code == arg[1]
|
||||
|
||||
|
||||
def test_user_update(u1_s1, u2_s1,u1_s2):
|
||||
# can update own user
|
||||
r = u1_s1.patch(
|
||||
reverse(
|
||||
DETAIL_URL,
|
||||
args={auth.get_user(u1_s1).id}
|
||||
),
|
||||
{'first_name': 'test'},
|
||||
content_type='application/json'
|
||||
)
|
||||
response = json.loads(r.content)
|
||||
assert r.status_code == 200
|
||||
assert response['first_name'] == 'test'
|
||||
|
||||
# can't update another user
|
||||
r = u1_s1.patch(
|
||||
reverse(
|
||||
DETAIL_URL,
|
||||
args={auth.get_user(u2_s1).id}
|
||||
),
|
||||
{'first_name': 'test'},
|
||||
content_type='application/json'
|
||||
)
|
||||
assert r.status_code == 403
|
||||
|
||||
r = u1_s1.patch(
|
||||
reverse(
|
||||
DETAIL_URL,
|
||||
args={auth.get_user(u1_s2).id}
|
||||
),
|
||||
{'first_name': 'test'},
|
||||
content_type='application/json'
|
||||
)
|
||||
assert r.status_code == 404
|
||||
@@ -47,7 +47,7 @@ router.register(r'sync', api.SyncViewSet)
|
||||
router.register(r'sync-log', api.SyncLogViewSet)
|
||||
router.register(r'unit', api.UnitViewSet)
|
||||
router.register(r'user-file', api.UserFileViewSet)
|
||||
router.register(r'user-name', api.UserNameViewSet, basename='username')
|
||||
router.register(r'user', api.UserViewSet)
|
||||
router.register(r'user-preference', api.UserPreferenceViewSet)
|
||||
router.register(r'user-space', api.UserSpaceViewSet)
|
||||
router.register(r'view-log', api.ViewLogViewSet)
|
||||
|
||||
@@ -53,7 +53,7 @@ from cookbook.helper.ingredient_parser import IngredientParser
|
||||
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest, CustomIsOwner,
|
||||
CustomIsOwnerReadOnly, CustomIsShare, CustomIsShared,
|
||||
CustomIsSpaceOwner, CustomIsUser, group_required,
|
||||
is_space_owner, switch_user_active_space, above_space_limit, CustomRecipePermission)
|
||||
is_space_owner, switch_user_active_space, above_space_limit, CustomRecipePermission, CustomUserPermission)
|
||||
from cookbook.helper.recipe_search import RecipeFacet, RecipeSearch
|
||||
from cookbook.helper.recipe_url_import import get_from_youtube_scraper, get_images_from_soup
|
||||
from cookbook.helper.scrapers.scrapers import text_scraper
|
||||
@@ -85,7 +85,7 @@ from cookbook.serializer import (AutomationSerializer, BookmarkletImportListSeri
|
||||
SupermarketCategoryRelationSerializer,
|
||||
SupermarketCategorySerializer, SupermarketSerializer,
|
||||
SyncLogSerializer, SyncSerializer, UnitSerializer,
|
||||
UserFileSerializer, UserNameSerializer, UserPreferenceSerializer,
|
||||
UserFileSerializer, UserSerializer, UserPreferenceSerializer,
|
||||
UserSpaceSerializer, ViewLogSerializer)
|
||||
from cookbook.views.import_export import get_integration
|
||||
from recipes import settings
|
||||
@@ -354,7 +354,7 @@ class TreeMixin(MergeMixin, FuzzyFilterMixin, ExtendedRecipeMixin):
|
||||
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class UserNameViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
class UserViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
list:
|
||||
optional parameters
|
||||
@@ -362,9 +362,9 @@ class UserNameViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
- **filter_list**: array of user id's to get names for
|
||||
"""
|
||||
queryset = User.objects
|
||||
serializer_class = UserNameSerializer
|
||||
permission_classes = [CustomIsGuest]
|
||||
http_method_names = ['get']
|
||||
serializer_class = UserSerializer
|
||||
permission_classes = [CustomUserPermission]
|
||||
http_method_names = ['get', 'patch']
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = self.queryset.filter(userspace__space=self.request.space)
|
||||
|
||||
@@ -96,7 +96,7 @@ def space_overview(request):
|
||||
if 'signup_token' in request.session:
|
||||
return HttpResponseRedirect(reverse('view_invite', args=[request.session.pop('signup_token', '')]))
|
||||
|
||||
create_form = SpaceCreateForm(initial={'name': f'{request.user.username}\'s Space'})
|
||||
create_form = SpaceCreateForm(initial={'name': f'{request.user.get_user_display_name()}\'s Space'})
|
||||
join_form = SpaceJoinForm()
|
||||
|
||||
return render(request, 'space_overview.html', {'create_form': create_form, 'join_form': join_form})
|
||||
|
||||
Reference in New Issue
Block a user