使用 VueJS、Flask 和 RethinkDB 实现简单文件存储服务的文件和文件夹模型
文件和文件夹模型
我们将创建简单的模型来处理文件和文件夹,类似于我们对用户模型所做的操作。我们将创建一个文件夹模型作为文件模型的子模型。
有关如何设置工作区的更多信息,请查看本系列的第一个指南:使用 VueJS、Flask 和 RethinkDB 构建简单文件存储服务的简介和设置。
文件模型
随着我们的继续,你会注意到,事实上文件是以平面方式存储在文件系统中的。所有用户都有一个文件夹,其中存储了他们的所有文件,但数据的结构是合乎逻辑的,并存储在数据库中。这样,我们对文件系统的写入操作就最少了。为此,我们将采用一些非常巧妙的技术,这些技术可能对你未来的项目有用。
在/api/models.py中创建基础模型
class File(RethinkDBModel):
_table = 'files'
class Folder(File):
pass
我们首先为 File 模型创建create()方法。当我们向用于创建文件的/users/<user_id>/files/<file_id>端点发出 POST 请求时,将调用此方法。
@classmethod
def create(cls, **kwargs):
name = kwargs.get('name')
size = kwargs.get('size')
uri = kwargs.get('uri')
parent = kwargs.get('parent')
creator = kwargs.get('creator')
# Direct parent ID
parent_id = '0' if parent is None else parent['id']
doc = {
'name': name,
'size': size,
'uri': uri,
'parent_id': parent_id,
'creator': creator,
'is_folder': False,
'status': True,
'date_created': datetime.now(r.make_timezone('+01:00')),
'date_modified': datetime.now(r.make_timezone('+01:00'))
}
res = r.table(cls._table).insert(doc).run(conn)
doc['id'] = res['generated_keys'][0]
if parent is not None:
Folder.add_object(parent, doc['id'])
return doc
首先,我们从关键字参数字典中收集所需的所有信息,如名称、大小、文件 URI、创建者等。我们收集了一个参数,我们称之为parent。此字段指向我们要存储此文件的文件夹的id。如果我们想将文件存储在根文件夹中,我们可以选择不传递此参数。随着我们的继续,您将了解如何利用它来创建复杂的嵌套文件夹结构。
请注意,如果父级为None , parent_id字段将为0。这可以处理创建文件时没有父级的情况。这假设我们将文件存储在 ID 为 0 的根文件夹中。
我们将有关文件的所有信息收集到一个字典中,然后调用insert()函数将其存储在数据库中。调用 insert 函数返回的字典包含新生成的文档的 ID。插入字典后,我们将 ID 信息填充到字典中,以便将其返回给用户。
在这个方法的最后三行中,我们添加了一个检查来查看父级是否为None。由于此文件管理器实现有文件夹,因此每当我们在文件夹中创建文件时,我们都必须从逻辑上将每个新创建的对象添加到文件夹中。我们通过将对象 ID 添加到我们尝试存储它的文件夹的相应记录中的对象列表中来实现这一点。这是通过调用我们将在 Folder 模型中创建的名为add_object的方法来完成的。
接下来,我们将回到我们的基础RethinkDBModel类来创建一些有用的方法,我们可能会在子类中覆盖这些方法,也可能不会。
class RethinkDBModel(object):
@classmethod
def find(cls, id):
return r.table(cls._table).get(id).run(conn)
@classmethod
def filter(cls, predicate):
return list(r.table(cls._table).filter(predicate).run(conn))
@classmethod
def update(cls, id, fields):
status = r.table(cls._table).get(id).update(fields).run(conn)
if status['errors']:
raise DatabaseProcessError("Could not complete the update action")
return True
@classmethod
def delete(cls, id):
status = r.table(cls._table).get(id).delete().run(conn)
if status['errors']:
raise DatabaseProcessError("Could not complete the delete action")
return True
这里我们为 RethinkDB get()、filter()、update()和delete()函数创建了包装方法。这样,子类可以利用这些函数进行更复杂的交互。
我们将在文件模型中创建的下一个方法是用于在文件夹之间移动文件的函数。
@classmethod
def move(cls, obj, to):
previous_folder_id = obj['parent_id']
previous_folder = Folder.find(previous_folder_id)
Folder.remove_object(previous_folder, obj['id'])
Folder.add_object(to, obj['id'])
这里的逻辑相当简单。当我们想要将文件obj移动到文件夹to时,我们调用此方法。
我们首先获取文件当前父目录的当前文件夹 ID。它存储在obj的parent_id字段中。我们调用 Folder 模型find函数来获取文件夹对象作为名为previous_folder的字典。获取此对象后,我们做两件事。我们从上一个文件夹previous_folder中删除文件对象,并将文件对象添加到新文件夹to 。我们通过调用 Folder 类的remove_object()和add_object()方法来实现这一点。这些方法分别从 Folder 文档中的对象列表中删除文件 ID 并将文件 ID 添加到其中。我稍后会展示这些实现的样子。
现在,我们已完成文件建模。我们可以对文件执行基本交互,如创建、编辑、从数据库中删除等。
接下来我们讨论文件夹模型的逻辑,这与我们对文件所做的非常相似。
文件夹模型
@classmethod
def create(cls, **kwargs):
name = kwargs.get('name')
parent = kwargs.get('parent')
creator = kwargs.get('creator')
# Direct parent ID
parent_id = '0' if parent is None else parent['id']
doc = {
'name': name,
'parent_id': parent_id,
'creator': creator,
'is_folder': True,
'last_index': 0,
'status': True,
'objects': None,
'date_created': datetime.now(r.make_timezone('+01:00')),
'date_modified': datetime.now(r.make_timezone('+01:00'))
}
res = r.table(cls._table).insert(doc).run(conn)
doc['id'] = res['generated_keys'][0]
if parent is not None:
cls.add_object(parent, doc['id'], True)
cls.tag_folder(parent, doc['id'])
return doc
@classmethod
def tag_folder(cls, parent, id):
tag = id if parent is None else '{}#{}'.format(parent['tag'], parent['last_index'])
cls.update(id, {'tag': tag})
这里的create()方法与我们用于文件的方法非常相似,但有一些修改。显然,首先要注意的是,我们只需要文件夹的名称和创建者即可创建文件夹。我们使用了类似的逻辑来确定父文件夹,并且最后还使用了 add_object ()方法将文件夹添加到父文件夹中。
我在这里包含了is_folder字段,对于文件夹,其默认值为True ,对于文件,其默认值为 False。
您会注意到,我们在这里调用了tag_folder()方法。稍后我们将需要该方法来处理移动文件夹。总而言之,文件夹根据其在文件树上的位置进行标记。索引基于它们在树上的级别。任何存储在根级别的文件夹都将具有<id>标签,其中 id 是文件夹的 ID。任何存储在该文件夹下的文件夹都将具有<id>-n的 id ,其中 n 是一个连续递增的整数。随后嵌套的文件夹将遵循相同的模式,并具有<id>-nm等的 id。随着我们添加更多文件夹, n将发生变化,我们将所需的数据存储在每个文件夹的last_index字段中,该字段默认为0 。当我们向此文件夹添加文件夹时,我们将增加last_index的值。tag_folder ()方法会处理所有这些。
我们通过调用insert()函数将我们创建的用于容纳所有这些数据的字典插入数据库。
接下来,我们将重写 File 类的 find 方法,以包含显示文件夹列表信息的功能。这对我们以后的前端非常有用。
@classmethod
def find(cls, id, listing=False):
file_ref = r.table(cls._table).get(id).run(conn)
if file_ref is not None:
if file_ref['is_folder'] and listing and file_ref['objects'] is not None:
file_ref['objects'] = list(r.table(cls._table).get_all(r.args(file_ref['objects'])).run(conn))
return file_ref
我们首先使用对象的 ID 获取对象。我们在满足三个条件时显示文件夹的列表:
- listing设置为 True。我们使用这个变量来了解我们是否确实需要有关文件夹中所含文件的信息。
- file_ref对象实际上是一个文件夹。我们通过检查文档的is_folder字段来确定这一点。
- 我们在文件夹文档的对象列表中拥有对象。
如果满足所有这些条件,我们将通过在文件表上调用get_all方法获取所有嵌套对象。此方法接受多个键并返回具有相应键的所有对象。我们使用r.args方法将对象列表转换为get_all方法的多个参数。我们用返回的列表替换文档的对象字段。此列表包含每个嵌套文件/文件夹的详细信息。
接下来,我们继续创建文件夹的移动方法。这将与我们之前为文件创建的移动方法非常相似,但包含处理标签的逻辑并确保我们能够真正移动文件夹。
@classmethod
def move(cls, obj, to):
if to is not None:
parent_tag = to['tag']
child_tag = obj['tag']
parent_sections = parent_tag.split("#")
child_sections = child_tag.split("#")
if len(parent_sections) > len(child_sections):
matches = re.match(child_tag, parent_tag)
if matches is not None:
raise Exception("You can't move this object to the specified folder")
previous_folder_id = obj['parent_id']
previous_folder = cls.find(previous_folder_id)
cls.remove_object(previous_folder, obj['id'])
if to is not None:
cls.add_object(to, obj['id'], True)
在这里,我们首先确保要移动到的文件夹确实已指定,并且不是None。这是因为这里的假设是,如果没有指定,我们实际上会将此文件夹移动到根文件夹。
我们获取了正在尝试移动的文件夹的标签。我们还获取了正在尝试将其移动到的文件夹的标签。然后,我们比较标签中的部分数量。这就是我们知道文件夹在文件树中的级别的方式。只有一种情况移动不是那么简单:当父部分多于子部分时(在这种情况下,父部分指的是我们尝试将此文件夹移动到的文件夹)。我们可以将文件夹移动到其级别及以上的任何文件夹,但如果 parent_sections多于child_sections ,我们就知道我们尝试将此文件夹移动到的文件夹可能嵌套在它自己的文件夹中。我们对此非常小心,因为如前所述,文件夹结构纯粹是合乎逻辑的,我们需要确保在此过程中不会出现错误。
如果我们要移动到的文件夹在文件树中位于我们要移动的文件夹下方,我们必须确保前一个文件夹不嵌套在后者中。这可以通过确保我们要移动的文件夹的child_tag不以parent_tag字符串开头来实现。我们使用正则表达式来实现这一点,如果发生这种情况,则引发异常。
我们快完成了!最后,我们将创建我之前提到的add_object()和remove_object()方法。
@classmethod
def remove_object(cls, folder, object_id):
update_fields = folder['objects'] or []
while object_id in update_fields:
update_fields.remove(object_id)
cls.update(folder['id'], {'objects': update_fields})
@classmethod
def add_object(cls, folder, object_id, is_folder=False):
p = {}
update_fields = folder['objects'] or []
update_fields.append(object_id)
if is_folder:
p['last_index'] = folder['last_index'] + 1
p['objects'] = update_fields
cls.update(folder['id'], p)
如前所述,我们将通过修改文件夹对象中的对象列表来执行添加和删除操作。添加子文件夹时,我们添加一个约束以更新文件夹的last_index变量。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~