Django 为每个模型字段设置隐私选项

Django set privacy options per model field

我已经完成了这个问题,,它的答案似乎没有解决我的问题,所以我在这里问一些相关的问题,

嗯,我有一个用户模型。我希望用户能够控制他们个人资料中每个字段的隐私(可能是 gendereducationinterests 等......)。

隐私选项不能仅限于私人或 public,而是描述性的

我也不希望将这些隐私选项保存在另一个模型上,但同样如此,通过一次查询我可以获得用户对象以及隐私选项。

所以如果我有 UserModel,

class User(models.Model):
    name = models.CharField()
    email = models.EmailField()
    phone = models.CharField()

如何在此处设置隐私设置?我正在使用 postgres,我可以映射一个 JSON 字段或 Hstore 甚至一个 ArrayField 吗?

人们过去用 Django 解决同样问题的最佳解决方案是什么?

更新:

我有 n 个模型字段。我真正想要的是将每个实例的隐私设置存储在自身或其他一些方便的方式上。

I also don't want these privacy options to be saved on another model, but the same so that with one query I could get the user object along with the privacy options.

我建议您将隐私对象与 UserModel 分离,以免将您的用户数据与这些选项混在一起。为了尽量减少数据库查询量,使用djangos select_related and prefetch_related.

您定义的 IMO 要求导致一组与隐私相关的对象,这些对象绑定到 UserModel。在这种情况下,django.contrib.auth 是一个很好的起点。它被构建为可扩展的。阅读有关该主题的 the docs

如果您期望有大量用户,因此还有更多的组,您可能需要考虑在基于 redis 的会话中为一个用户编写权限解析,以便能够在每次页面加载时快速获取它们.

更新:

我仔细考虑了您的要求,得出的结论是您需要 django-guardian 中实现的每个对象权限。您应该首先阅读他们的示例 代码。他们在 django.contrib.auth 之上构建它,但不依赖于它,这使得它也可用于遵循 django.contrib.auth.

中接口的自定义实现。

这样的事情怎么样?

class EditorList(models.Model):
    name = models.CharField(...)
    user = models.ForeignKey(User)
    editor = models.ManyToManyField(User)

class UserPermission(models.Model):
    user = models.ForeignKey(User)
    name = models.BooleanField(default=False)
    email = models.BooleanField(default=False)
    phone = models.BooleanField(default=False)
    ...
    editor = models.ManyToManyField(User)
    editor_list = models.ManyToManyField(EditorList)

如果用户想授予 public 'email' 权限,则她创建了一个 UserPermission editor=Noneeditor_list=None 以及 email=True.

如果她想允许用户 'rivadiz' 编辑她的电子邮件,那么她会创建一个 UserPermissioneditor='rivadiz'email=True

如果她想创建一个可以编辑她 phone 的朋友列表,那么她会创建并填充一个名为 'my_friends' 的 EditorList,然后创建一个 UserPermission editor_list='my_friends'phone=True

然后您应该能够查询有权编辑任何用户的任何字段的所有用户。 您可以在 User 模型中定义一些属性,以便在给定 Usereditor 的情况下轻松检查哪些字段是可编辑的。 您首先需要获取编辑器所属的所有 EditorLists,然后执行类似

的操作

perms = UserPermissions.objects.filter(user=self).filter(Q(editor=editor) | Q(editor_list=editor_list))

如果没有单独的权限模型,这将更加麻烦。您可以将单个用户个人资料的给定字段与多个朋友列表相关联这一事实意味着多对多 table,最好让 Django 为您处理。

我在想更像是:

class Visibility(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    field = models.CharField(max_length=32)
    public = models.BooleanField(default=False)
    friends = models.BooleanField(default=False)
    lists = models.ManyToManyField(FriendList)

    @staticmethod
    def visible_profile(request_user, profile_user):
        """Get a dictionary of profile_user's profile, as
           should be visible to request_user..."""

(我将把这种方法的细节留作练习,但它不是 太复杂了。)

我会提醒用户 设置 这些权限所涉及的 UI 可能是一个挑战,因为多对多连接到好友列表。不是不可能,当然,但有点乏味。

这里 M2M table 的一个主要优势是,如果用户或任何朋友列表被删除,它将自我维护——只有一个例外。这个方案的想法是,没有任何可见性记录,所有数据都是私有的(为了让每个人都能看到你的名字,你会添加一个可见性记录,用户=(你自己),字段="name",和public=True。由于 public=False、friends=False 和 lists=[] 的可见性记录毫无意义,因此我会在用户编辑后检查该情况并完全删除该记录。

另一种有效的策略是拥有两条特殊的 FriendList 记录:一条用于 "public",一条用于 "all friends"。这大大简化了可见性模型,但代价是在其他地方增加了一些代码。

我已经解决了我的问题,尝试了具有权限和其他关系的解决方案。我有一个 Relationship 模型,所有其他关系列表都来自 Relationship 模型,所以我不想维护一个单独的关系列表。

所以我的选择是使用 Postgres JSONField 或 HStoreField。由于 Django 对 postgres freatures 有很好的支持,我发现这些点对我的选择很有利。

  • JSON/HashStore可以用Django ORM查询
  • 配置简单JSON/HashStore,比权限和关系更容易编辑和维护。
  • 我发现使用权限的数据库查询时间比使用 JSON/HStore 的时间长。 (点击率更高)
  • 添加和验证每个字段的权限比 adding/validatingJSON.
  • 复杂
  • 在将来的某个时候如果出现更简单或更轻松的解决方案,我可以迁移到它,在单个字段中进行完整配置。

所以我的选择是使用配置模型。

class UserConfiguration(models.Model):
    user = # link to the user model
    configuration = #either an HStore of JSONFeild

然后编写了一个验证器来确保配置数据模型在保存和更新时不会被弄乱。我将字段分组以最小化验证字段。然后编写了一个简单的解析器,它获取用户并找到它们之间的关系,然后将配置映射到 return 允许的字段数据(在未优化的实现中以 2-4 毫秒记录,现在已经足够了)。 (有了权限,我需要维护一个单独的朋友列表,并且应该在更新隐私配置时更新所有组权限,然后我仍然必须验证权限并处理它,这可能比这个,要不是复杂系统的成本).

我认为这种方法也具有可扩展性,因为大部分处理都在 Python 中完成,并且数据库调用已尽可能减少。

更新

我进一步细化了数据库查询。在之前的实现中,用户之间的关系迭代,耗时约 1-2 毫秒,将此实现更改为 .value_list('relations', flat=True) 将查询时间减少到 400-520µs。

首先,在我看来你应该选择多个模型并使查询更快,正如其他答案中已经提到的,你可以使用缓存或 select_related 或 prefetch_related你的用例。

所以这是我提出的解决方案:

用户模型

class User(models.Model):
    name = models.CharField()
    email = models.EmailField()
    phone = models.CharField()
    ...
    public_allowed_read_fields = ArrayField(models.IntegerField())
    friends_allowed_read_fields = ArrayField(models.IntegerField())
    me_allowed_read_fields = ArrayField(models.IntegerField())
    friends = models.ManyToManyField(User)
    part_of = models.ManyToManyField(Group, through=GroupPrivacy)

群组(好友列表)模式

class Group(models.Model):
    name = models.CharField()

通过模型

class GroupPrivacy(models.Model):
    user = models.ForeignKey(User)
    group = models.ForeignKey(Group)
    allowed_read_fields = ArrayField(models.IntegerField())

映射到整数的用户模型字段

USER_FIELDS_MAPPING = (
    (1, User._meta.get_field('name')),
    (2, User._meta.get_field('email')),
    (3, User._meta.get_field('phone')),
    ...
)

这有什么帮助??

  • 对于每个 publicfriendsme,您可以在用户模型本身中拥有一个字段,如上所述,即 public_allowed_read_fieldsfriends_allowed_read_fieldsme_allowed_read_fields。每个字段都将包含映射到 USER_FIELDS_MAPPING(下面详细解释)

  • 中的整数列表
  • 对于 friend_list_1,您将拥有名为 friend_list_1 的组。现在重点是用户想要显示或隐藏此朋友列表的一组特定字段。这就是 through 模型 GroupPrivacy 发挥作用的地方。使用此直通模型,您可以定义用户和组之间的 M2M 关系,以及此关系独有的一些附加属性。在这个 GroupPrivacy 模型中你可以看到 allowed_read_fields 字段,它用于存储与 USER_FIELDS_MAPPING[ 中的整数对应的整数数组=57=]。所以可以说,对于组 friend_list_1 和用户 Aallowed_read_fields = [1,2]。现在,如果将其映射到 USER_FIELDS_MAPPING,您将知道用户 A 只想向该列表中的朋友显示姓名和电子邮件。同样,friend_list_1 组中的不同用户将在 allowed_read_fields 中为其相应的 GroupPrivacy 模型实例具有不同的值。

多个组的情况类似。