Repository: ilyachch/django-rest-framework-rusdoc Branch: master Commit: 5a30aa5af782 Files: 53 Total size: 600.5 KB Directory structure: gitextract_s0pt2_8x/ ├── .files_cache.json ├── .github/ │ └── workflows/ │ ├── check_all_temp_data_cleared.yml │ └── check_source_for_changes.yml ├── .gitignore ├── .scripts/ │ ├── commit_changes.py │ └── monitor_repository.py ├── README.md ├── SUMMARY.md ├── api-guide/ │ ├── authentication.md │ ├── caching.md │ ├── content-negotiation.md │ ├── exceptions.md │ ├── fields.md │ ├── filtering.md │ ├── format-suffixes.md │ ├── generic-views.md │ ├── metadata.md │ ├── pagination.md │ ├── parsers.md │ ├── permissions.md │ ├── relations.md │ ├── renderers.md │ ├── requests.md │ ├── responses.md │ ├── reverse.md │ ├── routers.md │ ├── schemas.md │ ├── serializers.md │ ├── settings.md │ ├── status-codes.md │ ├── testing.md │ ├── throttling.md │ ├── validators.md │ ├── versioning.md │ ├── views.md │ └── viewsets.md ├── monitoring_config.toml ├── quickstart.md ├── topics/ │ ├── ajax-csrf-cors.md │ ├── browsable-api.md │ ├── browser-enhancements.md │ ├── documenting-your-api.md │ ├── html-and-forms.md │ ├── internationalization.md │ ├── rest-hypermedia-hateoas.md │ └── writable-nested-serializers.md ├── topics.md └── tutorial/ ├── 1-serialization.md ├── 2-requests-and-responses.md ├── 3-class-based-views.md ├── 4-authentication-and-permissions.md ├── 5-relationships-and-hyperlinked-apis.md └── 6-viewsets-and-routers.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .files_cache.json ================================================ { ".reference/README.md": { "commit_hash": "18c5883be8ad3c5c5c36a5e39855d79ac80de7ca", "file_hash": "3040917d581be433da899a2b37135150" }, ".reference/api-guide/authentication.md": { "commit_hash": "d0a5d5e7cad7f1032b4d0a36cab1596076f705ad", "file_hash": "18084ecacf507bd6fef2c23ba2a82a56" }, ".reference/api-guide/caching.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "51b470c630a4e77a032b0ed152e130cf" }, ".reference/api-guide/content-negotiation.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "553eb6d9511eef8f816a9e8b628f321b" }, ".reference/api-guide/exceptions.md": { "commit_hash": "c0202a0aa5cbaf8573458b932878dfd5044c93ab", "file_hash": "659b7ca76f8efccef817591fef9d13fd" }, ".reference/api-guide/fields.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "fd8aca55b6366ed2b616b41b2ecc80ff" }, ".reference/api-guide/filtering.md": { "commit_hash": "a323cf7c0a33d7ffd395a6805019f613fb79f985", "file_hash": "54dc859f64dc2c74e12f6b644732c066" }, ".reference/api-guide/format-suffixes.md": { "commit_hash": "041b88f8bbb48d9688ebd5add294eee2dfc93d1c", "file_hash": "60ea26c1f58e88b82b31b49e524316e6" }, ".reference/api-guide/generic-views.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "2245d2ff68c620c1b85ff7aebfa31e19" }, ".reference/api-guide/metadata.md": { "commit_hash": "e045dc465270c18689dba4a970378cd9744e57b6", "file_hash": "a0fac04282c4c1ecff4856ff2b9920cc" }, ".reference/api-guide/pagination.md": { "commit_hash": "f74a44e850a685ac73c819ae7b96b0d68a8f734f", "file_hash": "10ba91c105ba55ba25b6df2d69ba8da5" }, ".reference/api-guide/parsers.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "aabe99bc6bb768328673587bfbd496fb" }, ".reference/api-guide/permissions.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "44218348009641ebc15786966c82ec5c" }, ".reference/api-guide/relations.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "b0e775bb98155170eba917bc2d563110" }, ".reference/api-guide/renderers.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "1639f64a8b74fb7a41c1cb915c75bd20" }, ".reference/api-guide/requests.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "6128e6f6fa93cff0b4970fcf6c9fd351" }, ".reference/api-guide/responses.md": { "commit_hash": "ade172e1d5db87dc86bc616cbb4df7ccd2eb2fd3", "file_hash": "949dd98307af0923652f9b6538ae088f" }, ".reference/api-guide/reverse.md": { "commit_hash": "332e9560ab0b3a1b8c0ab8f68e95c09bc2d8999f", "file_hash": "df00e0304464472fda1e97c804e1c9f9" }, ".reference/api-guide/routers.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "76e6a45fc7886c10514fa8fd9fd304df" }, ".reference/api-guide/schemas.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "db37b906afd6550a53477ecdd266f2c6" }, ".reference/api-guide/serializers.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "06cc1fe1cabdb28f42b0762eaab8cbbb" }, ".reference/api-guide/settings.md": { "commit_hash": "f9f10e041f9b2a2c936ee54a437d4c255f76e626", "file_hash": "e1745bd2d62b873253d4e8f338569b4f" }, ".reference/api-guide/status-codes.md": { "commit_hash": "3e052376ace520135d42dcf52f35a51b7dcc5ac2", "file_hash": "2c87ce34b589a196dc98c77eb3490884" }, ".reference/api-guide/testing.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "e0120b9a199bd3825e703b71d2b5dae9" }, ".reference/api-guide/throttling.md": { "commit_hash": "d0a5d5e7cad7f1032b4d0a36cab1596076f705ad", "file_hash": "433ab273878e04287d91b9cf0eb803e6" }, ".reference/api-guide/validators.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "86e050e68e80d8328f03ea507425c50c" }, ".reference/api-guide/versioning.md": { "commit_hash": "0d6589cf45940bb67ace74a06b2c5b053f1c31ef", "file_hash": "ff8cfbbbe7d70ab6bd0eac5300989be1" }, ".reference/api-guide/views.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "e4ce6ac6dd7dbb59eb5f9e94e1f54a7a" }, ".reference/api-guide/viewsets.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "32a13e45a120c83ef706f908775e5888" }, ".reference/topics/ajax-csrf-cors.md": { "commit_hash": "605cc4f7367f58002056453d9befd3c1918f6a38", "file_hash": "6122ed56c57770bb7d15175a6f99a598" }, ".reference/topics/browsable-api.md": { "commit_hash": "ade172e1d5db87dc86bc616cbb4df7ccd2eb2fd3", "file_hash": "6e26d5f207a3298f7b186c00e8a159f6" }, ".reference/topics/browser-enhancements.md": { "commit_hash": "f5470ab9e292c7321377ae1f43f85e311d94975f", "file_hash": "8cfe1ff6085397161e067af7bd694a6e" }, ".reference/topics/documenting-your-api.md": { "commit_hash": "e794e5e5e43d6838d9ffb8eb0a505b5f531b261f", "file_hash": "85ce53b26dbb566993378b57614a2eb2" }, ".reference/topics/html-and-forms.md": { "commit_hash": "9d4ed054bf8acfac6209b7e7f837fc97517affcc", "file_hash": "d74c3a05790e8a201b4a373d54f01267" }, ".reference/topics/internationalization.md": { "commit_hash": "2ca3b7f9c449145156f0b0e2d9ea58a2295f8fbf", "file_hash": "3df3355efbbdf65a01dcec2b3f0de844" }, ".reference/topics/rest-hypermedia-hateoas.md": { "commit_hash": "ade172e1d5db87dc86bc616cbb4df7ccd2eb2fd3", "file_hash": "2f55490daba3d605c7c75be9760a4929" }, ".reference/topics/writable-nested-serializers.md": { "commit_hash": "f0dbf0a264677f2a53faab402ff49f442fc4383a", "file_hash": "ae7df9cb54197166d97df23d51bdda26" }, ".reference/tutorial/1-serialization.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "87c06f99ba8316fa40da167990b20c86" }, ".reference/tutorial/2-requests-and-responses.md": { "commit_hash": "c0f3649224117609d19e79c77242b525570d25c0", "file_hash": "4c863e0a540e66f4034f378b23d2d4f9" }, ".reference/tutorial/3-class-based-views.md": { "commit_hash": "c0f3649224117609d19e79c77242b525570d25c0", "file_hash": "ed6ae64942ff177d051f7bbf90a08637" }, ".reference/tutorial/4-authentication-and-permissions.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "c3fc89a9fae949e7a27f84ef98865300" }, ".reference/tutorial/5-relationships-and-hyperlinked-apis.md": { "commit_hash": "e221d9a1d6638b936707efc390adff59511a6605", "file_hash": "0ca0fcc63848bbc4df6ad75739ff4077" }, ".reference/tutorial/6-viewsets-and-routers.md": { "commit_hash": "c0f3649224117609d19e79c77242b525570d25c0", "file_hash": "85f48f71db392be56df18888bb993530" }, ".reference/tutorial/quickstart.md": { "commit_hash": "c0f3649224117609d19e79c77242b525570d25c0", "file_hash": "0b31dc44cc6fb7e4a983149e96bdd175" } } ================================================ FILE: .github/workflows/check_all_temp_data_cleared.yml ================================================ name: Check all temp data cleared on: pull_request: branches: - master jobs: check_no_temp_files_left: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Check no temp files left run: | if [[ $(find .reference -type f -name "*.temp" -o -name "*.tmp" -o -name "*.md" | wc -l) -gt 0 ]]; then exit 1 fi - name: Check CHANGES.md cleared run: | if [[ $(find -name "CHANGES.md" | wc -l) -gt 0 ]]; then exit 1 fi ================================================ FILE: .github/workflows/check_source_for_changes.yml ================================================ name: Check source for changes on: schedule: - cron: '0 0 * * 0' workflow_dispatch: inputs: dry_run: description: 'Dry run' required: false default: false type: boolean permissions: contents: write pull-requests: write jobs: check_source_for_changes: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 with: python-version: '3.12' - name: Check for changes id: check run: | EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) echo "changes<<$EOF" >> "$GITHUB_OUTPUT" python3 ./.scripts/monitor_repository.py --details-file CHANGES.md >> "$GITHUB_OUTPUT" echo "$EOF" >> "$GITHUB_OUTPUT" - name: Report env: CHANGES: ${{ steps.check.outputs.changes }} run: | echo "Changes:" echo "$CHANGES" - name: Check if branch already exists id: check_branch_exists if: steps.check.outputs.changes != '' && !inputs.dry_run run: | git fetch if git branch -r | grep -q "origin/sync-with-original"; then echo echo "branch_exists=1" >> $GITHUB_OUTPUT exit 1 fi - name: Open PR if: steps.check.outputs.changes != '' && steps.check_branch_exists.outputs != '1' && !inputs.dry_run uses: peter-evans/create-pull-request@v8 with: title: Sync with original body: ${{ steps.check.outputs.changes }} labels: sync branch: sync-with-original branch-suffix: random commit-message: Sync with original delete-branch: true assignees: ${{ github.repository_owner }} draft: true ================================================ FILE: .gitignore ================================================ .idea # Node rules: ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt ## Dependency directory ## Commenting this out is preferred by some people, see ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git node_modules # Book build output _book # eBook build output *.epub *.mobi *.pdf ================================================ FILE: .scripts/commit_changes.py ================================================ #!/usr/bin/env python3 import argparse import dataclasses import logging import subprocess from pathlib import Path from typing import Dict, List, Tuple logger = logging.getLogger(__name__) @dataclasses.dataclass class FileStatus: status: str path: Path @property def is_modified(self) -> bool: return 'M' in self.status @property def is_deleted(self) -> bool: return 'D' in self.status @property def is_in_reference(self) -> bool: return str(self.path).startswith('.reference/') @dataclasses.dataclass class GitChanges: file_pairs: List[Tuple[Path, Path]] skipped_reference_files: List[Path] unpaired_modified_files: List[Path] def run_command(command: List[str], cwd: Path | None = None) -> str: """Выполняет команду и возвращает результат""" logger.debug(f'Running command: {" ".join(command)}') process = subprocess.run( command, cwd=cwd, capture_output=True, text=True, ) if process.returncode != 0: raise RuntimeError(f'Command failed: {process.stderr}') return process.stdout.strip() def get_git_status() -> str: """Получает статус git в формате porcelain""" return run_command(['git', 'status', '--porcelain']) def parse_status_line(line: str) -> FileStatus | None: """Разбирает строку статуса git и возвращает FileStatus""" if not line: return None # Формат вывода git status --porcelain: # XY PATH или XY "PATH с пробелами" parts = line.split(maxsplit=1) if len(parts) != 2: return None status = parts[0].strip() file_path = parts[1].strip() return FileStatus(status=status, path=Path(file_path)) def get_changes_to_commit() -> GitChanges: """ Находит файлы для коммитов: - пары: измененный файл + удаленный файл в .reference с тем же относительным путем - одиночные удаленные файлы из .reference (без измененного аналога) - измененные файлы без удаленного файла в .reference """ status_output = get_git_status() modified_files: Dict[Path, FileStatus] = {} deleted_reference_files: Dict[Path, FileStatus] = {} for line in status_output.split('\n'): if not line: continue file_status = parse_status_line(line) if not file_status: continue # Измененные файлы (не в .reference) if file_status.is_modified and not file_status.is_in_reference: modified_files[file_status.path] = file_status # Удаленные файлы в .reference if file_status.is_deleted and file_status.is_in_reference: deleted_reference_files[file_status.path] = file_status matched_reference_files: set[Path] = set() # Находим пары файлов и отмечаем соответствующие файлы в .reference file_pairs = [] unpaired_modified_files: List[Path] = [] for modified_path in modified_files: reference_path = Path('.reference') / modified_path if reference_path in deleted_reference_files: file_pairs.append((modified_path, reference_path)) matched_reference_files.add(reference_path) else: logger.warning(f'No matching deleted reference file for {modified_path}') unpaired_modified_files.append(modified_path) # Находим удаленные файлы в .reference без пары skipped_reference_files = [ ref_path for ref_path in deleted_reference_files if ref_path not in matched_reference_files ] return GitChanges( file_pairs=file_pairs, skipped_reference_files=skipped_reference_files, unpaired_modified_files=unpaired_modified_files, ) def commit_file_pair(modified_file: Path, reference_file: Path, dry_run: bool = False) -> None: """Создает коммит для пары файлов""" file_name = modified_file.name commit_message = f'update {file_name}' logger.info(f'Committing changes for {file_name}...') if dry_run: logger.info(f'[DRY RUN] Would add: {modified_file} {reference_file}') logger.info(f'[DRY RUN] Would commit with message: {commit_message}') return # Добавляем файлы в индекс run_command(['git', 'add', str(modified_file), str(reference_file)]) # Создаем коммит run_command(['git', 'commit', '-m', commit_message]) logger.info(f'Committed: {commit_message}') def commit_skipped_reference_file(reference_file: Path, dry_run: bool = False) -> None: """Создает коммит для удаленного файла из .reference без пары""" file_name = reference_file.name commit_message = f'skipped {file_name}' logger.info(f'Committing skipped reference file {file_name}...') if dry_run: logger.info(f'[DRY RUN] Would add: {reference_file}') logger.info(f'[DRY RUN] Would commit with message: {commit_message}') return run_command(['git', 'add', str(reference_file)]) run_command(['git', 'commit', '-m', commit_message]) logger.info(f'Committed: {commit_message}') def main() -> None: parser = argparse.ArgumentParser( description='Create separate commits for each pair of related files' ) parser.add_argument( '--dry-run', action='store_true', default=False, help='Run without making actual commits', ) parser.add_argument( '-v', '--verbose', action='count', default=0, help='Increase verbosity (can be used multiple times)', ) args = parser.parse_args() dry_run, verbose = args.dry_run, args.verbose log_level = (5 - verbose) * 10 logging.basicConfig( level=log_level, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', ) logger.info('Starting file pair commit process') logger.debug(f'Arguments: dry_run={dry_run}, verbose={verbose}') try: changes = get_changes_to_commit() if changes.unpaired_modified_files: logger.warning( 'Modified files without matching deleted reference: %s', ', '.join(str(path) for path in changes.unpaired_modified_files), ) if not changes.file_pairs and not changes.skipped_reference_files: logger.warning('No matching file pairs or skipped reference files found') return if changes.file_pairs: logger.info(f'Found {len(changes.file_pairs)} file pairs to commit') if changes.skipped_reference_files: logger.info( f'Found {len(changes.skipped_reference_files)} skipped reference files to commit' ) for modified_file, reference_file in changes.file_pairs: commit_file_pair(modified_file, reference_file, dry_run) for reference_file in changes.skipped_reference_files: commit_skipped_reference_file(reference_file, dry_run) logger.info('All commits created successfully!') except Exception as e: logger.error(f'Error: {e}') exit(1) if __name__ == '__main__': main() ================================================ FILE: .scripts/monitor_repository.py ================================================ import argparse import dataclasses import hashlib import json import logging import subprocess import tempfile from functools import cache, cached_property from pathlib import Path import tomllib logger = logging.getLogger(__name__) @dataclasses.dataclass class Config: repo_url: str repo_branch: str original_link: str paths: list[tuple[str, str]] cache_file: Path @classmethod def from_config_file(cls, config_file_path: Path) -> 'Config': content = config_file_path.read_text() raw_config = tomllib.loads(content) logger.debug('Loaded config: %s', raw_config) return cls( repo_url=raw_config['repository']['repo_url'], repo_branch=raw_config['repository']['repo_branch'], original_link=raw_config['repository']['original_link'], paths=raw_config['repository']['paths'], cache_file=Path(raw_config['repository']['cache_file']), ) @dataclasses.dataclass class CacheData: commit_hash: str | None file_hash: str | None def read_files_json(path: Path) -> dict[str, CacheData]: if not path.exists(): return {} raw_content = json.loads(path.read_text()) return { key: CacheData(commit_hash=value.get('commit_hash'), file_hash=value.get('file_hash')) for key, value in raw_content.items() } def write_files_json(path: Path, data: dict[str, CacheData]) -> None: path.parent.mkdir(parents=True, exist_ok=True) keys = sorted(data.keys()) path.write_text( json.dumps( { key: {'commit_hash': data[key].commit_hash, 'file_hash': data[key].file_hash} for key in keys }, indent=2, ) ) @dataclasses.dataclass class FileInfo: src: Path dst: Path _repo: 'GitRepository' def __hash__(self): return hash(f'{self.src}{self.dst}') @cached_property def file_hash(self) -> str: return hashlib.md5(self.src.read_bytes()).hexdigest() @cached_property def commit_hash(self) -> str: result = subprocess.run( ['git', 'rev-list', '-1', 'HEAD', '--', self.src.resolve()], cwd=self._repo.path, capture_output=True, text=True, check=True, ) return result.stdout.strip() @cache def get_file_diff( self, old_commit: str, ) -> str: result = subprocess.run( ['git', 'diff', old_commit, self.commit_hash, '--', self.src.resolve()], cwd=self._repo.path, capture_output=True, text=True, check=True, ) clear_result = result.stdout.strip() clear_result = '\n'.join(clear_result.split('\n')[4:]) clear_result = f'{old_commit} -> {self.commit_hash}\n{clear_result}' return clear_result def copy(self) -> None: self.dst.parent.mkdir(parents=True, exist_ok=True) self.dst.write_text(self.src.read_text()) class GitRepository: def __init__(self, config: 'Config', path: Path) -> None: self.config = config self.path = path @classmethod def get(cls, config: 'Config', temp_dir: Path | None) -> 'GitRepository': if temp_dir is None: logger.info('Creating temporary directory') temp_dir = Path(tempfile.mkdtemp()) logger.debug('Temporary directory: %s', temp_dir) if not temp_dir.exists() or not any(temp_dir.iterdir()): logger.debug('Cloning repository') subprocess.run( ['git', 'clone', config.repo_url, temp_dir.resolve()], check=True, capture_output=True, ) subprocess.run( ['git', 'checkout', config.repo_branch], cwd=temp_dir, check=True, capture_output=True, ) return cls(config, temp_dir) if any(temp_dir.iterdir()) and '.git' not in [x.name for x in temp_dir.iterdir()]: raise ValueError(f'{temp_dir} is not empty and not a git repository') check_result = subprocess.run( ['git', 'config', '--get', 'remote.origin.url'], cwd=temp_dir, capture_output=True, text=True, ) if check_result.stdout.strip() != config.repo_url: raise ValueError( f'Expected repository url {config.repo_url}, got {check_result.stdout.strip()}' ) subprocess.run( ['git', 'checkout', config.repo_branch], cwd=temp_dir, check=True, capture_output=True, ) subprocess.run( ['git', 'pull'], cwd=temp_dir, check=True, capture_output=True, ) return cls(config, temp_dir) def get_files_info(self) -> list[FileInfo]: file_infos: list[FileInfo] = [] for src_raw, dst_raw in self.config.paths: src, dst = self.path / Path(src_raw), Path(dst_raw) if not src.exists(): logging.warning(f'Path {src.resolve()} does not exist') continue if src.is_dir(): for folder, _, files in src.walk(): for src_file in files: src_file = Path(folder) / src_file dst_file = dst / src_file.relative_to(src) file_infos.append(FileInfo(src=src_file, dst=dst_file, _repo=self)) else: src_file = self.path / Path(src_raw) dst_file = Path(dst_raw) file_infos.append(FileInfo(src=src_file, dst=dst_file, _repo=self)) logger.debug('File infos: %s', file_infos) return file_infos def delete(self) -> None: logger.debug('Deleting folder %s', self.path) self.delete_folder(self.path) logger.debug('Folder %s deleted', self.path) def delete_folder(self, path: Path) -> None: for sub in path.iterdir(): if sub.is_dir(): self.delete_folder(sub) else: sub.unlink() path.rmdir() def process_repository( save: bool, tmp_folder_path: Path | None, dry_run: bool, config_file: Path, cache_file: Path, details_file: Path | None, ) -> None: config = Config.from_config_file(config_file) files_cache = read_files_json(cache_file or config.cache_file) obsolete_files = set(files_cache.keys()) changed_files: dict[Path, str] = {} repo = GitRepository.get(config, tmp_folder_path) file_infos = repo.get_files_info() for file_info in file_infos: file_cache_data = files_cache.get(str(file_info.dst)) if file_cache_data is None: if not dry_run: file_info.copy() changed_files[file_info.dst] = 'New file' else: if file_cache_data.commit_hash != file_info.commit_hash: if file_cache_data.file_hash != file_info.file_hash: if not dry_run: file_info.copy() changed_files[file_info.dst] = file_info.get_file_diff( file_cache_data.commit_hash ) files_cache[str(file_info.dst)] = CacheData( commit_hash=file_info.commit_hash, file_hash=file_info.file_hash, ) obsolete_files.discard(str(file_info.dst)) for obsolete_file in obsolete_files: logging.info('Removing obsolete file %s', ', '.join(obsolete_file)) del files_cache[obsolete_file] if not dry_run: logger.info('Writing cache file %s', cache_file) write_files_json(cache_file, files_cache) if changed_files: summary_lines = [f'Sync with [original]({config.original_link})\n'] details_lines = [f'Sync with [original]({config.original_link})\n'] for file, change in changed_files.items(): summary_lines.append(f'- [ ] `{file}`') change_message = change if change != 'New file' else 'New file' details_lines.append(f' - `{file}`\n') details_lines.append(f'```\n{change_message}\n```\n\n\n') summary_text = "\n".join(summary_lines) details_text = "".join(details_lines) print(summary_text) if details_file and not dry_run: details_file.write_text(details_text) if not save: logger.info('Deleting temporary folder %s', repo.path) repo.delete() if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument( '--config', default='monitoring_config.toml', required=False, help='Path to the configuration file', type=Path, ) parser.add_argument( '--cache', default='.files_cache.json', required=False, help='Path to the cache file', type=Path, ) parser.add_argument( '--details-file', default=None, required=False, help='Path to the file where detailed changes will be written', type=Path, ) parser.add_argument( '--temp-folder', default=None, required=False, help='Temporary folder to store files', type=Path, ) parser.add_argument( '-s', '--save', action='store_true', default=False, help='Save files in temporary folder', ) parser.add_argument( '--dry-run', action='store_true', default=False, help='Run without making changes', ) parser.add_argument( '-v', '--verbose', action='count', default=0, ) args = parser.parse_args() verbose, save, temp_folder_path, dry_run = ( args.verbose, args.save, args.temp_folder, args.dry_run, ) config_file, cache_file = args.config, args.cache logging.basicConfig(level=(5 - verbose) * 10) logger.info('Processing repository') logger.debug( 'Running with params %s', ', '.join(f'{key}={value}' for key, value in vars(args).items()) ) process_repository(save, temp_folder_path, dry_run, config_file, cache_file, args.details_file) ================================================ FILE: README.md ================================================ # Django REST framework ![Logo by Jake 'Sid' Smith](https://github.com/encode/django-rest-framework/raw/main/docs/img/logo.png) Django REST framework - это мощный и гибкий набор инструментов для создания Web API. Некоторые причины, по которым вы можете захотеть использовать REST framework: * Web-интерфейс API - огромный выигрыш в удобстве использования для ваших разработчиков. * [Политики аутентификации](api-guide/authentication.md), включая пакеты для [OAuth1a](api-guide/authentication.md#django-rest-framework-oauth) и [OAuth2](api-guide/authentication.md#django-oauth-toolkit). * [Сериализация](api-guide/serializers.md), поддерживающая как [ORM](api-guide/serializers.md#modelserializer), так и [non-ORM](api-guide/serializers.md#сериализаторы) источники данных. * Настраивается все - просто используйте [обычные представления на основе функций](api-guide/views#Представления-на-основе-функций), если вам не нужны [более](api-guide/generic-views.md) [мощные](api-guide/viewsets.md) [возможности](api-guide/routers.md). * Обширная документация и [отличная поддержка сообщества](https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework). * Используется и пользуется доверием всемирно известных компаний, включая [Mozilla](https://www.mozilla.org/en-us/about/), [Red Hat](https://www.redhat.com/), [Heroku](https://www.heroku.com/) и [Eventbrite](https://www.eventbrite.co.uk/about/). --- ## Требования REST framework требует следующего: * Django (4.2, 5.0, 5.1, 5.2) * Python (3.10, 3.11, 3.12, 3.13, 3.14) Мы **настоятельно рекомендуем** и официально поддерживаем только последние выпуски патчей каждой версии Python и Django. Следующие пакеты являются необязательными: * [PyYAML](https://pypi.org/project/pyyaml/), [uritemplate](https://pypi.org/project/uritemplate/) (5.1+, 3.0.0+) - Поддержка генерации схем. * [Markdown](https://pypi.org/project/markdown/) (3.3.0+) - Поддержка Markdown для Web-интерфейса API. * [Pygments](https://pypi.org/project/pygments/) (2.7.0+) - Добавление подсветки синтаксиса в обработку Markdown. * [django-filter](https://pypi.org/project/django-filter/) (1.0.1+) - Поддержка фильтрации. * [django-guardian](https://github.com/django-guardian/django-guardian) (1.1.1+) - Поддержка разрешений на уровне объектов. ## Установка Установите с помощью `pip`, включая все дополнительные пакеты, которые вы хотите... ```bash pip install djangorestframework pip install markdown # Markdown support for the browsable API. pip install django-filter # Filtering support ``` ...или клонируйте проект с github. ```bash git clone https://github.com/encode/django-rest-framework ``` Добавьте `'rest_framework'` в настройку `INSTALLED_APPS`. ```python INSTALLED_APPS = [ ... 'rest_framework', ] ``` Если вы планируете использовать Web-интерфейс API, вы, вероятно, также захотите добавить представления входа и выхода из системы REST framework. Добавьте следующее в ваш корневой файл `urls.py`. ```python urlpatterns = [ ... path('api-auth/', include('rest_framework.urls')) ] ``` Обратите внимание, что путь URL может быть любым, какой вы захотите. ## Пример Давайте рассмотрим быстрый пример использования REST-фреймворка для создания простого API с поддержкой модели. Мы создадим API с функцией чтения-записи для доступа к информации о пользователях нашего проекта. Все глобальные настройки для API REST-фреймворка хранятся в одном конфигурационном словаре с именем `REST_FRAMEWORK`. Начните с добавления следующих параметров в модуль `settings.py`: ```python REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' ] } ``` Не забудьте убедиться, что вы также добавили `rest_framework` в `INSTALLED_APPS`. Теперь мы готовы к созданию нашего API. Вот корневой модуль нашего проекта `urls.py`: ```python from django.urls import path, include from django.contrib.auth.models import User from rest_framework import routers, serializers, viewsets # Serializers define the API representation. class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = User fields = ['url', 'username', 'email', 'is_staff'] # ViewSets define the view behavior. class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer # Routers provide an easy way of automatically determining the URL conf. router = routers.DefaultRouter() router.register(r'users', UserViewSet) # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. urlpatterns = [ path('', include(router.urls)), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) ] ``` Теперь вы можете открыть API в браузере по адресу [http://127.0.0.1:8000/](http://127.0.0.1:8000/) и просмотреть новых "пользователей" API. Если вы используете элемент управления входом в систему в правом верхнем углу, вы также сможете добавлять, создавать и удалять пользователей из системы. ## Быстрый старт Не можете дождаться начала работы? Руководство [quickstart](quickstart.md) - это самый быстрый способ начать работу и создавать API с помощью REST framework. ## Руководство Руководство проведет вас через все этапы настройки DRF. Это займет не очень много времени, однако вы получите полное понимание того, как все компоненты работают друг с другом и данное руководство крайне рекомендовано к прочтению. * [Сериализация](tutorial/1-serialization.md) * [Запросы-ответы](tutorial/2-requests-and-responses.md) * [Представления-классы](tutorial/3-class-based-views.md) * [Аутентификация/права доступа](tutorial/4-authentication-and-permissions.md) * [Отношения и связи](tutorial/5-relationships-and-hyperlinked-apis.md) * [Наборы представлений и роутеры](tutorial/6-viewsets-and-routers.md) Так же есть пример работающего API законченного руководства для тестовых целей, [доступен здесь](http://restframework.herokuapp.com/). ## Навигатор по API Навигатор по API - исчерпывающее руководство по всему функционалу, предоставляемому DRF. * [Запросы](api-guide/requests.md) * [Ответы](api-guide/responses.md) * [Представления](api-guide/views.md) * [Общие представления](api-guide/generic-views.md) * [Viewsets](api-guide/viewsets.md) * [Маршрутизаторы](api-guide/routers.md) * [Парсеры](api-guide/parsers.md) * [Рендереры](api-guide/renderers.md) * [Сериализаторы](api-guide/serializers.md) * [Поля сериализатора](api-guide/fields.md) * [Отношения сериализаторов](api-guide/relations.md) * [Валидаторы](api-guide/validators.md) * [Аутентификация](api-guide/authentication.md) * [Разрешения](api-guide/permissions.md) * [Кэширование](api-guide/caching.md) * [Дросселирование](api-guide/throttling.md) * [Фильтрация](api-guide/filtering.md) * [Пагинация](api-guide/pagination.md) * [Версионирование](api-guide/versioning.md) * [Согласование контента](api-guide/content-negotiation.md) * [Метаданные](api-guide/metadata.md) * [Schemas](api-guide/schemas.md) * [Cуффиксы формата](api-guide/format-suffixes.md) * [Возвращение URL-адресов](api-guide/reverse.md) * [Исключения](api-guide/exceptions.md) * [Коды состояния](api-guide/status-codes.md) * [Тестирование](api-guide/testing.md) * [Настройки](api-guide/settings.md) ## Статьи Основные руководства для использующих DRF. * [AJAX, CSRF & CORS](topics/ajax-csrf-cors.md) * [The Browsable API](topics/browsable-api.md) * [Улучшения в браузере](topics/browser-enhancements.md) * [Документирование вашего API](topics/documenting-your-api.md) * [HTML и формы](topics/html-and-forms.md) * [Интернационализация](topics/internationalization.md) * [REST, гипермедиа и HATEOAS](topics/rest-hypermedia-hateoas.md) * [Вложенные сериализаторы с возможностью записи](topics/writable-nested-serializers.md) ## Разработка Смотрите [руководство для разработчиков](https://www.django-rest-framework.org/community/contributing/) для получения информации о том, как клонировать репозиторий, запустить набор тестов и внести изменения в REST Framework. ## Поддержка За поддержкой обращайтесь в [REST framework discussion group](https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework), попробуйте использовать канал `#restframework` на `irc.libera.chat`, или задайте вопрос на [Stack Overflow](https://stackoverflow.com/), обязательно указав тег ['django-rest-framework'](https://stackoverflow.com/questions/tagged/django-rest-framework). ## Безопасность **Пожалуйста, сообщайте о проблемах безопасности по электронной почте security@encode.io**. После этого сопровождающие проекта будут работать с вами над решением любых вопросов, если потребуется, до обнародования информации. --- ## Авторы перевода * [Ilya Chichak](https://github.com/ilyachch/) ## Помощь в переводе * [https://github.com/pymq](https://github.com/pymq) * [https://github.com/rufatpro](https://github.com/rufatpro) * [Dmitry Plaxunov](https://github.com/fojetin) Спасибо всем за помощь в переводе! Перевод производится с помощью утилиты [md_docs-trans-app](https://github.com/ilyachch/md_docs-trans-app) ================================================ FILE: SUMMARY.md ================================================ # Summary ## Overview * [Django REST framework](README.md) * [Быстрый старт](quickstart.md) * [Сериализация](tutorial/1-serialization.md) * [Запросы-ответы](tutorial/2-requests-and-responses.md) * [Представления-классы](tutorial/3-class-based-views.md) * [Аутентификация/права доступа](tutorial/4-authentication-and-permissions.md) * [Отношения и связи](tutorial/5-relationships-and-hyperlinked-apis.md) * [Наборы представлений и роутеры](tutorial/6-viewsets-and-routers.md) * Навигация по API: * [Запросы](api-guide/requests.md) * [Ответы](api-guide/responses.md) * [Представления](api-guide/views.md) * [Общие представления](api-guide/generic-views.md) * [Viewsets](api-guide/viewsets.md) * [Маршрутизаторы](api-guide/routers.md) * [Парсеры](api-guide/parsers.md) * [Рендереры](api-guide/renderers.md) * [Сериализаторы](api-guide/serializers.md) * [Поля сериализатора](api-guide/fields.md) * [Отношения сериализаторов](api-guide/relations.md) * [Валидаторы](api-guide/validators.md) * [Аутентификация](api-guide/authentication.md) * [Разрешения](api-guide/permissions.md) * [Кэширование](api-guide/caching.md) * [Дросселирование](api-guide/throttling.md) * [Фильтрация](api-guide/filtering.md) * [Пагинация](api-guide/pagination.md) * [Версионирование](api-guide/versioning.md) * [Согласование контента](api-guide/content-negotiation.md) * [Метаданные](api-guide/metadata.md) * [Schemas](api-guide/schemas.md) * [Cуффиксы формата](api-guide/format-suffixes.md) * [Возвращение URL-адресов](api-guide/reverse.md) * [Исключения](api-guide/exceptions.md) * [Коды состояния](api-guide/status-codes.md) * [Тестирование](api-guide/testing.md) * [Настройки](api-guide/settings.md) ## Статьи * [Статьи](topics.md) * [AJAX, CSRF & CORS](topics/ajax-csrf-cors.md) * [The Browsable API](topics/browsable-api.md) * [Улучшения в браузере](topics/browser-enhancements.md) * [Документирование вашего API](topics/documenting-your-api.md) * [HTML и формы](topics/html-and-forms.md) * [Интернационализация](topics/internationalization.md) * [REST, гипермедиа и HATEOAS](topics/rest-hypermedia-hateoas.md) * [Вложенные сериализаторы с возможностью записи](topics/writable-nested-serializers.md) ================================================ FILE: api-guide/authentication.md ================================================ # Аутентификация > Аутентификация должна быть подключаемой. > > - Джейкоб Каплан-Мосс, ["Худшие практики REST"](https://jacobian.org/writing/rest-worst-practices/) Аутентификация - это механизм связывания входящего запроса с набором идентификационных данных, таких как пользователь, от которого пришел запрос, или токен, которым он был подписан. Политики [permission](permissions.md) и [throttling](throttling.md) могут затем использовать эти учетные данные, чтобы определить, должен ли запрос быть разрешен. DRF предоставляет несколько схем аутентификации из коробки, а также позволяет реализовать пользовательские схемы. Аутентификация всегда выполняется в самом начале представления, до того, как произойдет проверка разрешений и дросселирование, и до того, как будет разрешено выполнение любого другого кода. Свойство `request.user` обычно устанавливается на экземпляр класса `User` пакета `contrib.auth`. Свойство `request.auth` используется для любой дополнительной информации об аутентификации, например, оно может быть использовано для представления маркера аутентификации, которым был подписан запрос. --- **Примечание:** Не забывайте, что **аутентификация сама по себе не разрешает и не запрещает входящий запрос**, она просто идентифицирует учетные данные, с которыми был сделан запрос. Информацию о том, как настроить политику разрешений для вашего API, смотрите в документации [permissions](permissions.md). --- ## Как определяется аутентификация Схемы аутентификации всегда определяются как список классов. DRF попытается выполнить аутентификацию с каждым классом в списке, и установит `request.user` и `request.auth`, используя возвращаемое значение первого класса, который успешно аутентифицируется. Если ни один класс не выполнит аутентификацию, `request.user` будет установлен в экземпляр `django.contrib.auth.models.AnonymousUser`, а `request.auth` будет установлен в `None`. Значение `request.user` и `request.auth` для неаутентифицированных запросов можно изменить с помощью параметров `UNAUTHENTICATED_USER` и `UNAUTHENTICATED_TOKEN`. ## Установка схемы аутентификации Схемы аутентификации по умолчанию можно установить глобально, используя параметр `DEFAULT_AUTHENTICATION_CLASSES`. Например. ```python REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication', ] } ``` Вы также можете установить схему аутентификации для каждого представления или каждого набора представлений, используя класс `APIView`, основанный на представлениях. ```python from rest_framework.authentication import SessionAuthentication, BasicAuthentication from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView class ExampleView(APIView): authentication_classes = [SessionAuthentication, BasicAuthentication] permission_classes = [IsAuthenticated] def get(self, request, format=None): content = { 'user': str(request.user), # `django.contrib.auth.User` instance. 'auth': str(request.auth), # None } return Response(content) ``` Или, если вы используете декоратор `@api_view` с представлениями, основанными на функциях. ```python @api_view(['GET']) @authentication_classes([SessionAuthentication, BasicAuthentication]) @permission_classes([IsAuthenticated]) def example_view(request, format=None): content = { 'user': str(request.user), # `django.contrib.auth.User` instance. 'auth': str(request.auth), # None } return Response(content) ``` ## Неавторизованные и запрещенные ответы Когда неаутентифицированному запросу отказано в разрешении, существует два различных кода ошибок, которые могут быть уместны. * [HTTP 401 Unauthorized](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2) * [HTTP 403 Permission Denied](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4) Ответы HTTP 401 всегда должны содержать заголовок `WWW-Authenticate`, который указывает клиенту, как пройти аутентификацию. Ответы HTTP 403 не включают заголовок `WWW-Authenticate`. Тип ответа, который будет использоваться, зависит от схемы аутентификации. Хотя может использоваться несколько схем аутентификации, для определения типа ответа может использоваться только одна схема. **Первый класс аутентификации, установленный в представлении, используется при определении типа ответа**. Обратите внимание, что когда запрос может успешно пройти аутентификацию, но при этом получить отказ в разрешении на выполнение запроса, в этом случае всегда будет использоваться ответ `403 Permission Denied`, независимо от схемы аутентификации. ## Django 5.1+ `LoginRequiredMiddleware` Если вы работаете с Django 5.1+ и используете [`LoginRequiredMiddleware`](https://docs.djangoproject.com/en/stable/ref/middleware/#django.contrib.auth.middleware.LoginRequiredMiddleware), обратите внимание, что все представления из DRF отказываются от этого промежуточного ПО. Это связано с тем, что аутентификация в DRF основана на классах аутентификации и разрешений, которые могут быть определены после применения промежуточного ПО. Кроме того, если запрос не аутентифицирован, промежуточное ПО перенаправляет пользователя на страницу входа в систему, что не подходит для API-запросов, где предпочтительнее возвращать код состояния `401 Unauthorized`. DRF предлагает эквивалентный механизм для представлений DRF через глобальные настройки `DEFAULT_AUTHENTICATION_CLASSES` и `DEFAULT_PERMISSION_CLASSES`. Их следует изменить соответствующим образом, если вам нужно обеспечить вход в систему при запросах API. ## Специфическая конфигурация Apache mod_wsgi Обратите внимание, что при развертывании на [Apache using mod_wsgi](https://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIPassAuthorization.html) заголовок авторизации по умолчанию не передается приложению WSGI, так как предполагается, что аутентификация будет обрабатываться Apache, а не на уровне приложения. Если вы развертываете на Apache и используете любую аутентификацию, не основанную на сеансах, вам необходимо явно настроить mod_wsgi для передачи необходимых заголовков приложению. Это можно сделать, указав директиву `WSGIPassAuthorization` в соответствующем контексте и установив ее в значение `'On'`. ```apache # this can go in either server config, virtual host, directory or .htaccess WSGIPassAuthorization On ``` --- # API Reference ## BasicAuthentication Эта схема аутентификации использует [HTTP Basic Authentication](https://tools.ietf.org/html/rfc2617), подписанную именем пользователя и паролем. Базовая аутентификация обычно подходит только для тестирования. При успешной аутентификации `BasicAuthentication` предоставляет следующие учетные данные. * `request.user` будет экземпляром Django `User`. * `request.auth` будет `None`. Ответы без аутентификации, которым отказано в разрешении, приведут к ответу `HTTP 401 Unauthorized` с соответствующим заголовком WWW-Authenticate. Например: ```http WWW-Authenticate: Basic realm="api" ``` --- **Примечание:** Если вы используете `BasicAuthentication` в реальном проекте, вы должны убедиться, что ваш API доступен только через `https`. Вы также должны убедиться, что клиенты вашего API всегда будут повторно запрашивать имя пользователя и пароль при входе в систему и никогда не будут сохранять эти данные в постоянном хранилище. --- ## TokenAuthentication --- **Примечание:** Аутентификация с помощью токенов, предоставляемая DRF, является довольно простой реализацией. Для реализации, которая позволяет использовать более одного токена на пользователя, имеет некоторые более жесткие детали реализации безопасности и поддерживает истечение срока действия токена, пожалуйста, обратитесь к стороннему пакету [Django REST Knox](https://github.com/James1345/django-rest-knox). --- Эта схема аутентификации использует простую схему аутентификации HTTP на основе токенов. Токен-аутентификация подходит для клиент-серверных установок, таких как собственные настольные и мобильные клиенты. Для использования схемы `TokenAuthentication` вам необходимо [настроить классы аутентификации](#установка-схемы-аутентификации), чтобы включить `TokenAuthentication`, и дополнительно включить `rest_framework.authtoken` в настройку `INSTALLED_APPS`: ```python INSTALLED_APPS = [ ... 'rest_framework.authtoken' ] ``` Обязательно запустите `manage.py migrate` после изменения настроек. Приложение `rest_framework.authtoken` обеспечивает миграцию баз данных Django. Вам также потребуется создать токены для своих пользователей. ```python from rest_framework.authtoken.models import Token token = Token.objects.create(user=...) print(token.key) ``` Для аутентификации клиентов ключ токена должен быть включен в HTTP-заголовок `Authorization`. Ключ должен иметь префикс в виде строкового литерала "Token", с пробелами, разделяющими эти две строки. Например: ```http Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b ``` *Если вы хотите использовать другое ключевое слово в заголовке, например `Bearer`, просто создайте подкласс `TokenAuthentication` и установите переменную класса `keyword`. При успешной аутентификации `TokenAuthentication` предоставляет следующие учетные данные. * `request.user` будет экземпляром Django `User`. * `request.auth` будет экземпляром `rest_framework.authtoken.models.Token`. Ответы без аутентификации, которым отказано в разрешении, приведут к ответу `HTTP 401 Unauthorized` с соответствующим заголовком WWW-Authenticate. Например: ```http WWW-Authenticate: Token ``` Инструмент командной строки `curl` может быть полезен для тестирования API с аутентификацией токенов. Например: ```http curl -X GET http://127.0.0.1:8000/api/example/ -H 'Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b' ``` --- **Примечание:** Если вы используете `TokenAuthentication` в реальном проекте, вы должны убедиться, что ваш API доступен только через `https`. --- ### Генерация токенов #### С помощью сигналов Если вы хотите, чтобы у каждого пользователя был автоматически сгенерированный токен, вы можете просто перехватить сигнал `post_save` пользователя. ```python from django.conf import settings from django.db.models.signals import post_save from django.dispatch import receiver from rest_framework.authtoken.models import Token @receiver(post_save, sender=settings.AUTH_USER_MODEL) def create_auth_token(sender, instance=None, created=False, **kwargs): if created: Token.objects.create(user=instance) ``` Обратите внимание, что вам нужно убедиться, что вы поместили этот фрагмент кода в установленный модуль `models.py` или в другое место, которое будет импортироваться Django при запуске. Если вы уже создали несколько пользователей, вы можете сгенерировать токены для всех существующих пользователей следующим образом: ```python from django.contrib.auth.models import User from rest_framework.authtoken.models import Token for user in User.objects.all(): Token.objects.get_or_create(user=user) ``` #### Посредством конечной точки API При использовании `TokenAuthentication` вы можете захотеть предоставить клиентам механизм для получения токена, заданного именем пользователя и паролем. DRF предоставляет встроенное представление для обеспечения такого поведения. Чтобы использовать его, добавьте представление `obtain_auth_token` в URLconf: ```python from rest_framework.authtoken import views urlpatterns += [ path('api-token-auth/', views.obtain_auth_token) ] ``` Обратите внимание, что URL часть шаблона может быть любой, которую вы хотите использовать. Представление `obtain_auth_token` вернет ответ в формате JSON, если действительные поля `username` и `password` будут отправлены в представление с помощью данных формы или JSON: ```python { 'token' : '9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b' } ``` Обратите внимание, что представление по умолчанию `obtain_auth_token` явно использует JSON запросы и ответы, а не использует классы рендерера и парсера по умолчанию в ваших настройках. По умолчанию к представлению `obtain_auth_token` не применяется никаких разрешений или дросселирования. Если вы хотите применить дросселирование, вам нужно переопределить класс представления и включить их с помощью атрибута `throttle_classes`. Если вам нужна настраиваемая версия представления `obtain_auth_token`, вы можете сделать это, создав подкласс класса представления `ObtainAuthToken` и используя его в url conf. Например, вы можете возвращать дополнительную информацию о пользователе помимо значения `token`: ```python from rest_framework.authtoken.views import ObtainAuthToken from rest_framework.authtoken.models import Token from rest_framework.response import Response class CustomAuthToken(ObtainAuthToken): def post(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.data, context={'request': request}) serializer.is_valid(raise_exception=True) user = serializer.validated_data['user'] token, created = Token.objects.get_or_create(user=user) return Response({ 'token': token.key, 'user_id': user.pk, 'email': user.email }) ``` И в вашем `urls.py`: ```python urlpatterns += [ path('api-token-auth/', CustomAuthToken.as_view()) ] ``` #### С администратором Django Токены также можно создавать вручную через интерфейс администратора. В случае, если вы используете большую базу пользователей, мы рекомендуем вам пропатчить класс `TokenAdmin`, чтобы настроить его под свои нужды, в частности, объявив поле `user` как `raw_field`. `ваше_приложение/admin.py`: ```python from rest_framework.authtoken.admin import TokenAdmin TokenAdmin.raw_id_fields = ['user'] ``` #### Использование команды Django manage.py Начиная с версии 3.6.4 можно сгенерировать пользовательский токен с помощью следующей команды: ```bash ./manage.py drf_create_token ``` эта команда вернет API-токен для данного пользователя, создав его, если он не существует: ```bash Generated token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b for user user1 ``` Если вы хотите восстановить токен (например, если он был скомпрометирован или произошла утечка), вы можете передать дополнительный параметр: ```bash ./manage.py drf_create_token -r ``` ## SessionAuthentication Эта схема аутентификации использует бэкенд сессий Django по умолчанию для аутентификации. Сеансовая аутентификация подходит для клиентов AJAX, которые работают в том же сеансовом контексте, что и ваш сайт. При успешной аутентификации `SessionAuthentication` предоставляет следующие учетные данные. * `request.user` будет экземпляром Django `User`. * `request.auth` будет `None`. Ответы без аутентификации, которым отказано в разрешении, приведут к ответу `HTTP 403 Forbidden`. Если вы используете API в стиле AJAX с `SessionAuthentication`, вам нужно убедиться, что вы включаете действительный CSRF токен для любых "небезопасных" вызовов HTTP методов, таких как `PUT`, `PATCH`, `POST` или `DELETE` запросы. Более подробную информацию смотрите в [Django CSRF documentation](https://docs.djangoproject.com/en/stable/howto/csrf/#using-csrf-protection-with-ajax). --- **Предупреждение**: Всегда используйте стандартное представление входа Django при создании страниц входа. Это обеспечит надлежащую защиту ваших представлений входа. --- Проверка CSRF в DRF работает несколько иначе, чем в стандартном Django, из-за необходимости поддерживать как сеансовую, так и несеансовую аутентификацию для одних и тех же представлений. Это означает, что только аутентифицированные запросы требуют CSRF-токенов, а анонимные запросы могут быть отправлены без CSRF-токенов. Такое поведение не подходит для представлений входа в систему, к которым всегда должна применяться проверка CSRF. ## RemoteUserAuthentication Эта схема аутентификации позволяет вам делегировать аутентификацию вашему веб-серверу, который устанавливает переменную окружения `REMOTE_USER`. Чтобы использовать его, вы должны иметь `django.contrib.auth.backends.RemoteUserBackend` (или его подкласс) в настройке `AUTHENTICATION_BACKENDS`. По умолчанию `RemoteUserBackend` создает объекты `User` для имен пользователей, которые еще не существуют. Чтобы изменить это и другое поведение, обратитесь к [документации Django](https://docs.djangoproject.com/en/stable/howto/auth-remote-user/). При успешной аутентификации `RemoteUserAuthentication` предоставляет следующие учетные данные: * `request.user` будет экземпляром Django `User`. * `request.auth` будет `None`. Обратитесь к документации вашего веб-сервера за информацией о настройке метода аутентификации, например: * [Apache Authentication How-To](https://httpd.apache.org/docs/2.4/howto/auth.html) * [NGINX (ограничение доступа)](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/) # Пользовательская аутентификация Чтобы реализовать собственную схему аутентификации, создайте подкласс `BaseAuthentication` и переопределите метод `.authenticate(self, request)`. Метод должен возвращать кортеж `(user, auth)`, если аутентификация прошла успешно, или `None` в противном случае. В некоторых случаях вместо возврата `None` вы можете захотеть вызвать исключение `AuthenticationFailed` из метода `.authenticate()`. Как правило, вам следует придерживаться следующего подхода: * Если попытка аутентификации не была предпринята, верните `None`. Любые другие схемы аутентификации, которые также используются, будут проверены. * Если попытка аутентификации была предпринята, но не удалась, вызовите исключение `AuthenticationFailed`. Ответ об ошибке будет возвращен немедленно, независимо от любых проверок разрешений и без проверки других схем аутентификации. Вы *можете* также переопределить метод `.authenticate_header(self, request)`. Если он реализован, он должен возвращать строку, которая будет использоваться в качестве значения заголовка `WWW-Authenticate` в ответе `HTTP 401 Unauthorized`. Если метод `.authenticate_header()` не переопределен, схема аутентификации будет возвращать ответы `HTTP 403 Forbidden`, когда неаутентифицированному запросу будет отказано в доступе. --- **Примечание:** Когда ваш пользовательский аутентификатор вызывается свойствами `.user` или `.auth` объекта запроса, вы можете увидеть, как `AttributeError` преобразуется в `WrappedAttributeError`. Это необходимо для того, чтобы исходное исключение не было подавлено доступом к внешнему свойству. Python не распознает, что `AttributeError` исходит от вашего пользовательского аутентификатора, и вместо этого будет считать, что объект запроса не имеет свойства `.user` или `.auth`. Эти ошибки должны быть исправлены или иным образом обработаны вашим аутентификатором. --- ## Пример Следующий пример аутентифицирует любой входящий запрос как пользователя, указанного в имени пользователя в пользовательском заголовке запроса 'X-USERNAME'. ```python from django.contrib.auth.models import User from rest_framework import authentication from rest_framework import exceptions class ExampleAuthentication(authentication.BaseAuthentication): def authenticate(self, request): username = request.META.get('HTTP_X_USERNAME') if not username: return None try: user = User.objects.get(username=username) except User.DoesNotExist: raise exceptions.AuthenticationFailed('No such user') return (user, None) ``` --- # Пакеты сторонних производителей Также доступны следующие пакеты сторонних производителей. ## django-rest-knox Библиотека [Django-rest-knox](https://github.com/James1345/django-rest-knox) предоставляет модели и представления для обработки аутентификации на основе токенов более безопасным и расширяемым способом, чем встроенная схема `TokenAuthentication` - с учетом одностраничных приложений и мобильных клиентов. Она предоставляет токены для каждого клиента, а также представления для их генерации при предоставлении другой аутентификации (обычно базовой), для удаления токена (обеспечивая принудительный выход с сервера) и для удаления всех токенов (выход из всех клиентов, в которые вошел пользователь). ## Django OAuth Toolkit Пакет [Django OAuth Toolkit](https://github.com/evonove/django-oauth-toolkit) обеспечивает поддержку OAuth 2.0 и работает с Python 3.4+. Пакет поддерживается [jazzband](https://github.com/jazzband/) и использует превосходный [OAuthLib](https://github.com/idan/oauthlib). Пакет хорошо документирован, хорошо поддерживается и в настоящее время является нашим **рекомендованным пакетом для поддержки OAuth 2.0**. ### Установка и настройка Установите с помощью `pip`. ```bash pip install django-oauth-toolkit ``` Добавьте пакет в `INSTALLED_APPS` и измените настройки DRF. ```python INSTALLED_APPS = [ ... 'oauth2_provider', ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', ] } ``` Более подробную информацию можно найти в документации [Django REST framework - Getting started](https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html). ## Django REST framework OAuth Пакет [Django REST framework OAuth](https://jpadilla.github.io/django-rest-framework-oauth/) обеспечивает поддержку OAuth1 и OAuth2 для DRF. Ранее этот пакет был включен непосредственно в DRF, но теперь поддерживается и сопровождается как пакет стороннего разработчика. ### Установка и настройка Установите пакет с помощью `pip`. ```bash pip install djangorestframework-oauth ``` Подробнее о настройке и использовании смотрите документацию по OAuth фреймворку Django REST для [authentication](https://jpadilla.github.io/django-rest-framework-oauth/authentication/) и [permissions](https://jpadilla.github.io/django-rest-framework-oauth/permissions/). ## JSON Web Token Authentication JSON Web Token - это довольно новый стандарт, который можно использовать для аутентификации на основе токенов. В отличие от встроенной схемы TokenAuthentication, аутентификация JWT не требует использования базы данных для проверки токена. Пакет для JWT-аутентификации - [djangorestframework-simplejwt](https://github.com/davesque/django-rest-framework-simplejwt), который предоставляет некоторые возможности, а также подключаемое приложение черного списка токенов. ## Аутентификация Hawk HTTP Библиотека [HawkREST](https://hawkrest.readthedocs.io/en/latest/) основана на библиотеке [Mohawk](https://mohawk.readthedocs.io/en/latest/) и позволяет вам работать с подписанными запросами и ответами [Hawk](https://github.com/hueniverse/hawk) в вашем API. [Hawk](https://github.com/hueniverse/hawk) позволяет двум сторонам безопасно общаться друг с другом, используя сообщения, подписанные общим ключом. Она основана на [HTTP MAC аутентификации доступа](https://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-05) (которая была основана на части [OAuth 1.0](https://oauth.net/core/1.0a/)). ## Аутентификация подписью HTTP HTTP Signature (в настоящее время [проект IETF](https://datatracker.ietf.org/doc/draft-cavage-http-signatures/)) предоставляет способ достижения аутентификации происхождения и целостности сообщений HTTP. Подобно схеме [Amazon's HTTP Signature scheme](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html), используемой многими сервисами компании, она допускает безэталонную аутентификацию по каждому запросу. [Elvio Toccalino](https://github.com/etoccalino/) поддерживает пакет [djangorestframework-httpsignature](https://github.com/etoccalino/django-rest-framework-httpsignature) (устаревший), который предоставляет простой в использовании механизм аутентификации HTTP Signature. Вы можете использовать обновленную версию-форк [djangorestframework-httpsignature](https://github.com/etoccalino/django-rest-framework-httpsignature), которой является [drf-httpsig](https://github.com/ahknight/drf-httpsig). ## Djoser [Djoser](https://github.com/sunscrapers/djoser) библиотека предоставляет набор представлений для обработки основных действий, таких как регистрация, вход в систему, выход из системы, сброс пароля и активация учетной записи. Пакет работает с пользовательской моделью пользователя и использует аутентификацию на основе токенов. Это готовая к использованию REST-реализация системы аутентификации Django. ## DRF Auth Kit Библиотека [DRF Auth Kit](https://github.com/huynguyengl99/drf-auth-kit) предоставляет современное решение для аутентификации REST с помощью файлов cookie JWT, входа через социальные сети, многофакторной аутентификации и комплексного управления пользователями. Пакет предлагает полную типобезопасность, автоматическую генерацию схемы OpenAPI с DRF Spectacular. Он поддерживает несколько типов аутентификации (JWT, DRF Token или Custom) и включает встроенную интернационализацию для более 50 языков. ## django-rest-auth / dj-rest-auth Эта библиотека предоставляет набор конечных точек REST API для регистрации, аутентификации (включая аутентификацию в социальных сетях), сброса пароля, получения и обновления данных пользователя и т.д. Имея эти конечные точки API, ваши клиентские приложения, такие как AngularJS, iOS, Android и другие, могут самостоятельно общаться с вашим бэкендом Django через REST API для управления пользователями. В настоящее время существует два форка этого проекта. * [Django-rest-auth](https://github.com/Tivix/django-rest-auth) - оригинальный проект, [но в настоящее время не получает обновлений](https://github.com/Tivix/django-rest-auth/issues/568). * [Dj-rest-auth](https://github.com/jazzband/dj-rest-auth) - более новый форк проекта. ## drf-social-oauth2 [Drf-social-oauth2](https://github.com/wagnerdelima/drf-social-oauth2) - это фреймворк, который поможет вам аутентифицироваться у основных поставщиков социального oauth2, таких как Facebook, Google, Twitter, Orcid и др. Он генерирует токены в виде JWT с простой настройкой. ## drfpasswordless [drfpasswordless](https://github.com/aaronn/django-rest-framework-passwordless) добавляет (по мотивам Medium, Square Cash) поддержку беспарольного входа в схему TokenAuthentication платформы DRF. Пользователи входят в систему и регистрируются с помощью токена, отправленного на контактную точку, например, адрес электронной почты или номер мобильного телефона. ## django-rest-authemail [django-rest-authemail](https://github.com/celiao/django-rest-authemail) предоставляет RESTful API интерфейс для регистрации и аутентификации пользователей. Для аутентификации используются адреса электронной почты, а не имена пользователей. Доступны конечные точки API для регистрации, проверки электронной почты при регистрации, входа в систему, выхода из системы, сброса пароля, проверки сброса пароля, изменения электронной почты, проверки изменения электронной почты, изменения пароля и детализации пользователя. Полностью функциональный пример проекта и подробные инструкции прилагаются. ## Django-Rest-Durin [Django-Rest-Durin](https://github.com/eshaan7/django-rest-durin) создана с идеей иметь одну библиотеку, которая делает аутентификацию токенов для нескольких Web/CLI/Mobile API клиентов через один интерфейс, но позволяет различную конфигурацию токенов для каждого API клиента, который потребляет API. Она обеспечивает поддержку нескольких токенов для каждого пользователя через пользовательские модели, представления, разрешения, которые работают с Django-Rest-Framework. Время истечения срока действия токена может быть разным для каждого API-клиента и настраивается через интерфейс администратора Django. Более подробную информацию можно найти в [Документации](https://django-rest-durin.readthedocs.io/en/latest/index.html). ## django_pyoidc [dango_pyoidc](https://github.com/makinacorpus/django_pyoidc) добавляет поддержку аутентификации OpenID Connect (OIDC). Это позволяет делегировать управление пользователями провайдеру идентификации, который может быть использован для реализации Single-Sign-On (SSO). Он обеспечивает поддержку большинства сценариев использования, таких как настройка сопоставления информации о токенах с моделями пользователей, использование аудиторий OIDC для контроля доступа и т. д. Более подробную информацию можно найти в [Документации](https://django-pyoidc.readthedocs.io/latest/index.html). ================================================ FILE: api-guide/caching.md ================================================ # Кэширование > У одной женщины было очень острое сознание, но почти не было памяти... Она помнила достаточно, чтобы работать, и она много работала. > > * Лидия Дэвис Кэширование в DRF хорошо работает с утилитами кэширования, предоставляемыми в Django. --- ## Использование кэша с apiview и наборами представлений Django предоставляет [`method_decorator`](https://docs.djangoproject.com/en/stable/topics/class-based-views/intro/#decorating-the-class) для использования декораторов с представлениями, основанными на классах. Его можно использовать с другими декораторами кэша, такими как [`cache_page`](https://docs.djangoproject.com/en/stable/topics/cache/#the-per-view-cache), [`vary_on_cookie`](https://docs.djangoproject.com/en/stable/topics/http/decorators/#django.views.decorators.vary.vary_on_cookie) и [`vary_on_headers`](https://docs.djangoproject.com/en/stable/topics/http/decorators/#django.views.decorators.vary.vary_on_headers). ```python from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page from django.views.decorators.vary import vary_on_cookie, vary_on_headers from rest_framework.response import Response from rest_framework.views import APIView from rest_framework import viewsets class UserViewSet(viewsets.ViewSet): # With cookie: cache requested url for each user for 2 hours @method_decorator(cache_page(60 * 60 * 2)) @method_decorator(vary_on_cookie) def list(self, request, format=None): content = { "user_feed": request.user.get_user_feed(), } return Response(content) class ProfileView(APIView): # With auth: cache requested url for each user for 2 hours @method_decorator(cache_page(60 * 60 * 2)) @method_decorator(vary_on_headers("Authorization")) def get(self, request, format=None): content = { "user_feed": request.user.get_user_feed(), } return Response(content) class PostView(APIView): # Cache page for the requested url @method_decorator(cache_page(60 * 60 * 2)) def get(self, request, format=None): content = { "title": "Post title", "body": "Post content", } return Response(content) ``` ## Использование кэша с декоратором @api_view При использовании декоратора `@api_view` декораторы кэша, основанные на методах, такие как [`cache_page`](https://docs.djangoproject.com/en/stable/topics/cache/#the-per-view-cache), [`vary_on_cookie`](https://docs.djangoproject.com/en/stable/topics/http/decorators/#django.views.decorators.vary.vary_on_cookie) и [`vary_on_headers`](https://docs.djangoproject.com/en/stable/topics/http/decorators/#django.views.decorators.vary.vary_on_headers) могут быть вызваны напрямую. ```python from django.views.decorators.cache import cache_page from django.views.decorators.vary import vary_on_cookie from rest_framework.decorators import api_view from rest_framework.response import Response @cache_page(60 * 15) @vary_on_cookie @api_view(["GET"]) def get_user_list(request): content = {"user_feed": request.user.get_user_feed()} return Response(content) ``` --- **Обратите внимание:** Декоратор [`cache_page`](https://docs.djangoproject.com/en/stable/topics/cache/#the-per-view-cache) кэширует только ответы `GET` и `HEAD` со статусом 200. --- ================================================ FILE: api-guide/content-negotiation.md ================================================ # Согласование контента > HTTP предусматривает несколько механизмов "согласования контента" - процесса выбора наилучшего представления для данного ответа при наличии нескольких представлений. > > - [RFC 2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html), Fielding et al. Согласование контента - это процесс выбора одного из нескольких возможных форматов ответа для возврата клиенту, основанный на предпочтениях клиента или сервера. ## Определение выбранного рендерера DRF использует простой стиль согласования контента для определения того, какой формат данных должен быть возвращен клиенту, основываясь на доступных рендерерах, приоритетах каждого из них и заголовке клиента `Accept:`. Используемый стиль частично зависит от клиента, а частично от сервера. 1. Более конкретным типам носителей отдается предпочтение перед менее конкретными типами носителей. 2. Если несколько типов медиа имеют одинаковую специфичность, то предпочтение отдается на основе порядка рендеринга, настроенного для данного представления. Например, при следующем заголовке `Accept`: ```http application/json; indent=4, application/json, application/yaml, text/html, */* ``` Приоритеты для каждого из указанных типов носителей будут следующими: * `'application/json; indent=4'` * `'application/json'`, `'application/yaml'` и `'text/html'` * `*/*` Если запрашиваемое представление было настроено только с рендерерами для `YAML` и `HTML`, то DRF будет выбирать тот рендерер, который указан первым в списке `renderer_classes` или настройке `DEFAULT_RENDERER_CLASSES`. Более подробную информацию о заголовке `HTTP Accept` смотрите в [RFC 2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html). --- **Примечание**: Значения "q" не учитываются DRF при определении предпочтений. Использование значений "q" негативно влияет на кэширование, и, по мнению автора, это ненужный и слишком сложный подход к согласованию контента. Это верный подход, поскольку спецификация HTTP намеренно не определяет, как сервер должен взвешивать предпочтения, основанные на сервере, против предпочтений, основанных на клиенте. --- # Переговоры по пользовательскому контенту Маловероятно, что вы захотите предоставить пользовательскую схему согласования контента для DRF, но вы можете сделать это при необходимости. Для реализации пользовательской схемы согласования контента переопределите `BaseContentNegotiation`. Классы согласования контента DRF обрабатывают выбор как подходящего парсера для запроса, так и подходящего рендерера для ответа, поэтому вы должны реализовать оба метода `.select_parser(request, parsers)` и `.select_renderer(request, renderers, format_suffix)`. Метод `select_parser()` должен вернуть один экземпляр парсера из списка доступных парсеров, или `None`, если ни один из парсеров не может обработать входящий запрос. Метод `select_renderer()` должен возвращать кортеж из (экземпляр рендерера, тип медиа), либо вызывать исключение `NotAcceptable`. ## Пример Ниже представлен пользовательский класс согласования контента, который игнорирует запрос клиента при выборе подходящего парсера или рендерера. ```python from rest_framework.negotiation import BaseContentNegotiation class IgnoreClientContentNegotiation(BaseContentNegotiation): def select_parser(self, request, parsers): """ Select the first parser in the `.parser_classes` list. """ return parsers[0] def select_renderer(self, request, renderers, format_suffix): """ Select the first renderer in the `.renderer_classes` list. """ return (renderers[0], renderers[0].media_type) ``` ## Указание согласования контента Класс согласования контента по умолчанию можно установить глобально, используя настройку `DEFAULT_CONTENT_NEGOTIATION_CLASS`. Например, следующие настройки будут использовать наш пример класса `IgnoreClientContentNegotiation`. ```python REST_FRAMEWORK = { 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'myapp.negotiation.IgnoreClientContentNegotiation', } ``` Вы также можете указать согласование контента, используемое для отдельного представления или набора представлений, используя представления на основе класса `APIView`. ```python from myapp.negotiation import IgnoreClientContentNegotiation from rest_framework.response import Response from rest_framework.views import APIView class NoNegotiationView(APIView): """ An example view that does not perform content negotiation. """ content_negotiation_class = IgnoreClientContentNegotiation def get(self, request, format=None): return Response({ 'accepted media type': request.accepted_renderer.media_type }) ``` ================================================ FILE: api-guide/exceptions.md ================================================ # Исключения > Исключения... позволяют чисто организовать обработку ошибок в центральном или высокоуровневом месте в структуре программы. > > — Даг Хеллманн, [Python Exception Handling Techniques](https://doughellmann.com/blog/2009/06/19/python-exception-handling-techniques/) ## Обработка исключений в представлениях DRF Представления DRF обрабатывают различные исключения и возвращают соответствующие ответы на ошибки. Обрабатываемыми исключениями являются: * Подклассы `APIException`, возникающие внутри DRF. * Исключение Django `Http404`. * Исключение Django `PermissionDenied`. В каждом случае DRF вернет ответ с соответствующим кодом состояния и типом содержимого. В теле ответа будут содержаться любые дополнительные сведения о характере ошибки. Большинство ответов на ошибки будут содержать ключ `detail` в теле ответа. Например, следующий запрос: ```http DELETE http://api.example.com/foo/bar HTTP/1.1 Accept: application/json ``` Может быть получен ответ об ошибке, указывающий на то, что метод `DELETE` не разрешен для данного ресурса: ```http HTTP/1.1 405 Method Not Allowed Content-Type: application/json Content-Length: 42 {"detail": "Method 'DELETE' not allowed."} ``` Ошибки валидации обрабатываются несколько иначе, и в качестве ключей в ответе будут указаны имена полей. Если ошибка валидации не относится к конкретному полю, то будет использоваться ключ "non_field_errors", или любое строковое значение, установленное для параметра `NON_FIELD_ERRORS_KEY`. Пример ошибки валидации может выглядеть следующим образом: ```http HTTP/1.1 400 Bad Request Content-Type: application/json Content-Length: 94 {"amount": ["A valid integer is required."], "description": ["This field may not be blank."]} ``` ## Пользовательская обработка исключений Вы можете реализовать пользовательскую обработку исключений, создав функцию-обработчик, которая преобразует исключения, возникающие в ваших представлениях API, в объекты ответа. Это позволяет вам контролировать стиль ответов на ошибки, используемый вашим API. Функция должна принимать пару аргументов, первый из которых - обрабатываемое исключение, а второй - словарь, содержащий любой дополнительный контекст, например, обрабатываемое в данный момент представление. Функция обработчика исключения должна либо возвращать объект `Response`, либо возвращать `None`, если исключение не может быть обработано. Если обработчик возвращает `None`, то исключение будет повторно поднято, и Django вернет стандартный ответ HTTP `500 "Server error"`. Например, вы можете захотеть убедиться, что все ответы на ошибки включают код состояния HTTP в теле ответа, например, так: ```http HTTP/1.1 405 Method Not Allowed Content-Type: application/json Content-Length: 62 {"status_code": 405, "detail": "Method 'DELETE' not allowed."} ``` Чтобы изменить стиль ответа, вы можете написать следующий пользовательский обработчик исключений: ```python from rest_framework.views import exception_handler def custom_exception_handler(exc, context): # Call REST framework's default exception handler first, # to get the standard error response. response = exception_handler(exc, context) # Now add the HTTP status code to the response. if response is not None: response.data['status_code'] = response.status_code return response ``` Аргумент `context` не используется обработчиком по умолчанию, но может быть полезен, если обработчику исключений нужна дополнительная информация, например, обрабатываемое в данный момент представление, доступ к которому можно получить как `context['view']`. Обработчик исключений также должен быть настроен в ваших настройках, используя ключ настройки `EXCEPTION_HANDLER`. Например: ```python REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler' } ``` Если параметр `'EXCEPTION_HANDLER'` не указан, по умолчанию используется стандартный обработчик исключений, предоставляемый DRF: ```python REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler' } ``` Обратите внимание, что обработчик исключений будет вызываться только для ответов, сгенерированных поднятыми исключениями. Он не будет использоваться для ответов, возвращаемых непосредственно представлением, таких как ответы `HTTP_400_BAD_REQUEST`, которые возвращаются общими представлениями при неудачной проверке сериализатора. --- # API Reference ## APIException **Сигнатура:** `APIException()`. **базовый класс** для всех исключений, возникающих внутри класса `APIView` или `@api_view`. Чтобы предоставить пользовательское исключение, подкласс `APIException` и установите атрибуты `.status_code`, `.default_detail` и `.default_code` для класса. Например, если ваш API полагается на сторонний сервис, который иногда может быть недоступен, вы можете захотеть реализовать исключение для кода ответа HTTP "503 Service Unavailable". Это можно сделать следующим образом: ```python from rest_framework.exceptions import APIException class ServiceUnavailable(APIException): status_code = 503 default_detail = 'Service temporarily unavailable, try again later.' default_code = 'service_unavailable' ``` #### Проверка исключений API Существует ряд различных свойств, доступных для проверки состояния исключения API. Вы можете использовать их для создания пользовательской обработки исключений для вашего проекта. Доступными атрибутами и методами являются: * `.detail` - Возвращает текстовое описание ошибки. * `.get_codes()` - Возвращает идентификатор кода ошибки. * `.get_full_details()` - Возвращает как текстовое описание, так и идентификатор кода. В большинстве случаев деталь ошибки будет простым элементом: ```python >>> print(exc.detail) You do not have permission to perform this action. >>> print(exc.get_codes()) permission_denied >>> print(exc.get_full_details()) {'message':'You do not have permission to perform this action.','code':'permission_denied'} ``` В случае ошибок валидации деталь ошибки будет представлять собой либо список, либо словарь элементов: ```python >>> print(exc.detail) {"name":"This field is required.","age":"A valid integer is required."} >>> print(exc.get_codes()) {"name":"required","age":"invalid"} >>> print(exc.get_full_details()) {"name":{"message":"This field is required.","code":"required"},"age":{"message":"A valid integer is required.","code":"invalid"}} ``` ## ParseError **Сигнатура:** `ParseError(detail=None, code=None)`. Возникает, если запрос содержит неправильно сформированные данные при доступе к `request.data`. По умолчанию это исключение приводит к ответу с кодом состояния HTTP "400 Bad Request". ## AuthenticationFailed **Сигнатура:** `AuthenticationFailed(detail=None, code=None)`. Возникает, когда входящий запрос содержит неправильную аутентификацию. По умолчанию это исключение приводит к ответу с кодом состояния HTTP "401 Unauthenticated", но оно также может привести к ответу "403 Forbidden", в зависимости от используемой схемы аутентификации. Более подробную информацию см. в документации [authentication documentation](authentication.md). ## NotAuthenticated **Сигнатура:** `NotAuthenticated(detail=None, code=None)`. Возникает, когда неаутентифицированный запрос не прошел проверку на разрешение. По умолчанию это исключение приводит к ответу с кодом состояния HTTP "401 Unauthenticated", но оно также может привести к ответу "403 Forbidden", в зависимости от используемой схемы аутентификации. Более подробную информацию см. в документации [authentication documentation](authentication.md). ## PermissionDenied **Сигнатура:** `PermissionDenied(detail=None, code=None)`. Возникает, когда аутентифицированный запрос не прошел проверку на разрешение. По умолчанию это исключение приводит к ответу с кодом состояния HTTP "403 Forbidden". ## NotFound **Сигнатура:** `NotFound(detail=None, code=None)`. Возникает, когда ресурс не существует по указанному URL. Это исключение эквивалентно стандартному исключению `Http404` Django. По умолчанию это исключение приводит к ответу с кодом состояния HTTP "404 Not Found". ## MethodNotAllowed **Сигнатура:** `MethodNotAllowed(method, detail=None, code=None)`. Возникает, когда происходит входящий запрос, который не сопоставлен с методом-обработчиком на представлении. По умолчанию это исключение приводит к ответу с кодом состояния HTTP "405 Method Not Allowed". ## Неприемлемо **Сигнатура:** `NotAcceptable(detail=None, code=None)`. Возникает, когда поступает запрос с заголовком `Accept`, который не может быть удовлетворен ни одним из доступных рендереров. По умолчанию это исключение приводит к ответу с кодом состояния HTTP "406 Not Acceptable". ## UnsupportedMediaType **Сигнатура:** `UnsupportedMediaType(media_type, detail=None, code=None)`. Возникает, если при обращении к `request.data` нет парсеров, способных обработать тип содержимого данных запроса. По умолчанию это исключение приводит к ответу с кодом состояния HTTP "415 Unsupported Media Type". ## Дроссель **Сигнатура:** `Throttled(wait=None, detail=None, code=None)`. Возникает, когда входящий запрос не проходит проверку на дросселирование. По умолчанию это исключение приводит к ответу с кодом состояния HTTP "429 Too Many Requests". ## ValidationError **Сигнатура:** `ValidationError(detail=None, code=None)`. Исключение `ValidationError` немного отличается от других классов `APIException`: * Аргумент `detail` может быть списком или словарем деталей ошибки, а также может быть вложенной структурой данных. Используя словарь, вы можете указать ошибки на уровне полей при выполнении проверки на уровне объектов в методе `validate()` сериализатора. Например. `raise serializers.ValidationError({'name': 'Please enter a valid name.'})`. * По соглашению вы должны импортировать модуль `serializers` и использовать полностью определенный `ValidationError`, чтобы отличить его от встроенной ошибки валидации Django. Например. `raise serializers.ValidationError('Это поле должно быть целочисленным значением.')`. Класс `ValidationError` должен использоваться для сериализатора и валидации полей, а также классами валидаторов. Он также вызывается при вызове `serializer.is_valid` с именованным аргументом `raise_exception`: ```python serializer.is_valid(raise_exception=True) ``` Общие представления используют флаг `raise_exception=True`, что означает, что вы можете переопределить стиль ответов на ошибки валидации глобально в вашем API. Для этого используйте пользовательский обработчик исключений, как описано выше. По умолчанию это исключение приводит к ответу с кодом состояния HTTP "400 Bad Request". --- # Общие представления об ошибках DRF предоставляет два представления ошибок, подходящих для предоставления общих JSON ответов `500` Server Error и `400` Bad Request. (Стандартные представления ошибок Django предоставляют HTML-ответы, которые могут не подойти для приложения, использующего только API). Используйте их согласно [документации по настройке представлений ошибок в Django](https://docs.djangoproject.com/en/stable/topics/http/views/#customizing-error-views). ## `rest_framework.exceptions.server_error` Возвращает ответ с кодом состояния `500` и типом содержимого `application/json`. Устанавливается как `handler500`: ```python handler500 = 'rest_framework.exceptions.server_error' ``` ## `rest_framework.exceptions.bad_request` Возвращает ответ с кодом статуса `400` и типом содержимого `application/json`. Устанавливается как `handler400`: ```python handler400 = 'rest_framework.exceptions.bad_request' ``` # Пакеты сторонних производителей Также доступны следующие пакеты сторонних производителей. ## Стандартизированные ошибки ДРФ Пакет [drf-standardized-errors](https://github.com/ghazi-git/drf-standardized-errors) предоставляет обработчик исключений, который генерирует одинаковый формат для всех ответов 4xx и 5xx. Он является заменой стандартного обработчика исключений и позволяет настраивать формат ответа на ошибку без переписывания всего обработчика исключений. Стандартизированный формат ответа на ошибку легче документировать и проще обрабатывать потребителям API. ================================================ FILE: api-guide/fields.md ================================================ # Поля сериализатора > Каждое поле в классе Form отвечает не только за проверку данных, но и за их "очистку" — приведение их к единообразному формату. > > — [Django documentation](https://docs.djangoproject.com/en/stable/ref/forms/api/#django.forms.Form.cleaned_data) Поля сериализатора выполняют преобразование между примитивными значениями и внутренними типами данных. Они также занимаются проверкой входных значений, а также получением и установкой значений из родительских объектов. --- **Примечание:** Поля сериализатора объявляются в `fields.py`, но по соглашению вы должны импортировать их с помощью `from rest_framework import serializers` и обращаться к полям как `serializers.`. --- ## Основные аргументы Каждый конструктор класса поля сериализатора принимает как минимум эти аргументы. Некоторые классы `Field` принимают дополнительные, специфические для поля аргументы, но следующие должны приниматься всегда: ### `read_only` Поля, доступные только для чтения, включаются в выходные данные API, но не должны включаться во входные данные при операциях создания или обновления. Любые поля `read_only`, по ошибке включенные во входные данные сериализатора, будут проигнорированы. Установите значение `True`, чтобы гарантировать, что поле используется при сериализации представления, но не используется при создании или обновлении экземпляра во время десериализации. По умолчанию `False`. ### `write_only` Установите значение `True`, чтобы поле могло использоваться при обновлении или создании экземпляра, но не включалось при сериализации представления. По умолчанию `False`. ### `required` Обычно ошибка возникает, если поле не предоставлено во время десериализации. Установите значение `False`, если это поле не обязательно должно присутствовать при десериализации. Установка этого значения в `False` также позволяет не выводить атрибут объекта или ключ словаря при сериализации экземпляра. Если ключ не присутствует, он просто не будет включен в выходное представление. По умолчанию имеет значение `True`. Если вы используете [Model Serializer](../api-guide/serializers.md#modelserializer), значение по умолчанию будет `False`, если вы указали `default` или если соответствующее поле `Model` имеет `blank=True` или `null=True` и в то же время не является частью ограничения по уникальности. (Обратите внимание, что без значения `default` [ограничения по уникальности приведут к тому, что поле станет обязательным.](../api-guide/validators.md#необязательные-поля).) ### `default` Если установлено, это значение по умолчанию, которое будет использоваться для поля, если значение не передано. Если значение не задано, то по умолчанию атрибут вообще не заполняется. Значение `default` не применяется во время операций частичного обновления. В случае частичного обновления только те поля, которые указаны во входящих данных, получат подтвержденное значение. Может быть задана как функция или другой вызываемый объект, в этом случае значение будет выполняться каждый раз при его использовании. При вызове оно не получает никаких аргументов. Если вызываемая функция имеет атрибут `requires_context = True`, то поле сериализатора будет передано в качестве аргумента. Например: ```python class CurrentUserDefault: """ May be applied as a `default=...` value on a serializer field. Returns the current user. """ requires_context = True def __call__(self, serializer_field): return serializer_field.context['request'].user ``` При сериализации экземпляра будет использоваться значение по умолчанию, если атрибут объекта или ключ словаря не присутствует в экземпляре. Обратите внимание, что установка значения `default` подразумевает, что поле не является обязательным. Включение обоих именованных аргументов `default` и `required` является недопустимым и приведет к ошибке. ### `allow_null` Обычно, если в поле сериализатора передается `None`, возникает ошибка. Установите этот именованный аргумент в `True`, если `None` должно считаться допустимым значением. Обратите внимание, что без явного указания `default` установка этого аргумента в `True` подразумевает `default` значение `null` для вывода сериализации, но не подразумевает значение по умолчанию для десериализации ввода. По умолчанию `False`. ### `source` Имя атрибута, который будет использоваться для заполнения поля. Может быть методом, который принимает только аргумент `self`, например `URLField(source='get_absolute_url')`, или может использовать точечную нотацию для обхода атрибутов, например `EmailField(source='user.email')`. При сериализации полей с точечной нотацией может потребоваться предоставить значение `default`, если какой-либо объект отсутствует или пуст при обходе атрибута. Остерегайтесь возможных проблем n+1 при использовании атрибута source, если вы обращаетесь к реляционной модели orm. Например: ```python class CommentSerializer(serializers.Serializer): email = serializers.EmailField(source="user.email") ``` В этом случае объект пользователя должен быть извлечен из базы данных, если он не был предварительно извлечен. Если это нежелательно, убедитесь, что вы используете методы `prefetch_related` и `select_related` соответствующим образом. Более подробную информацию об этих методах можно найти в [документации django](https://docs.djangoproject.com/en/stable/ref/models/querysets/#django.db.models.query.QuerySet.select_related). Значение `source='*'` имеет особое значение и используется для указания на то, что в поле должен быть передан весь объект. Это может быть полезно для создания вложенных представлений или для полей, которым требуется доступ к полному объекту для определения выходного представления. По умолчанию используется имя поля. ### `validators` Список функций валидаторов, которые должны быть применены к вводимому полю и которые либо выдают ошибку валидации, либо просто возвращаются. Функции валидатора обычно должны вызывать `serializers.ValidationError`, но встроенный в Django `ValidationError` также поддерживается для совместимости с валидаторами, определенными в кодовой базе Django или в сторонних пакетах Django. ### `error_messages` Словарь кодов ошибок и сообщений об ошибках. ### `label` Короткая текстовая строка, которая может быть использована в качестве имени поля в полях HTML-формы или других описательных элементах. ### `help_text` Текстовая строка, которая может быть использована в качестве описания поля в полях HTML-формы или других описательных элементах. ### `initial` Значение, которое должно использоваться для предварительного заполнения значений полей HTML-формы. Вы можете передать ему вызываемый объект, как и в случае с любым обычным полем Django `Field`: ```python import datetime from rest_framework import serializers class ExampleSerializer(serializers.Serializer): day = serializers.DateField(initial=datetime.date.today) ``` ### `style` Словарь пар ключ-значение, которые можно использовать для управления тем, как рендереры должны отображать поле. Двумя примерами здесь являются `'input_type'` и `'base_template'`: ```python # Use for the input. password = serializers.CharField( style={'input_type': 'password'} ) # Use a radio input instead of a select input. color_channel = serializers.ChoiceField( choices=['red', 'green', 'blue'], style={'base_template': 'radio.html'} ) ``` Более подробную информацию можно найти в документации [HTML & Forms](../topics/html-and-forms.md). --- # Булевы поля ## BooleanField Булево представление. При использовании HTML-кодированных форм ввода имейте в виду, что отсутствие значения всегда будет рассматриваться как установка поля в `False`, даже если для него указана опция `default=True`. Это происходит потому, что чекбоксы HTML представляют состояние без флажка в виде отсутствия значение, поэтому DRF воспринимает отсутствие значения как `False`. Обратите внимание, что в Django 2.1 из `models.BooleanField` был удален именованный аргумент `blank`. До Django 2.1 поля `models.BooleanField` всегда имели значение `blank=True`. Таким образом, начиная с Django 2.1 экземпляры `serializers.BooleanField` по умолчанию будут генерироваться без kwarg `required` (т.е. эквивалентно `required=True`), тогда как в предыдущих версиях Django экземпляры `BooleanField` по умолчанию будут генерироваться с опцией `required=False`. Если вы хотите управлять этим поведением вручную, явно объявите `BooleanField` в классе сериализатора или используйте опцию `extra_kwargs` для установки флага `required`. Соответствует `django.db.models.fields.BooleanField`. **Сигнатура:** `BooleanField()`. --- # Строковые поля ## CharField Текстовое представление. Опционально проверяет, чтобы текст был короче `max_length` и длиннее `min_length`. Соответствует `django.db.models.fields.CharField` или `django.db.models.fields.TextField`. **Сигнатура:** `CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)`. * `max_length` - Проверяет, что вводимые данные содержат не более этого количества символов. * `min_length` - Проверяет, что вводимые данные содержат не менее этого количества символов. * `allow_blank` - Если установлено значение `True`, то пустая строка будет считаться допустимым значением. Если установлено значение `False`, то пустая строка будет считаться недопустимой и вызовет ошибку валидации. По умолчанию `False`. * `trim_whitespace` - Если установлено значение `True`, то пробельные символы в начале и в конце будут обрезаны. По умолчанию `True`. Опция `allow_null` также доступна для строковых полей, хотя ее использование не рекомендуется в пользу `allow_blank`. Можно установить и `allow_blank=True`, и `allow_null=True`, но это означает, что для строковых представлений будут допустимы два разных типа пустых значений, что может привести к несоответствию данных и тонким ошибкам в работе приложения. ## EmailField Текстовое представление, проверяющее, является ли этот текст действительным адресом электронной почты. Соответствует `django.db.models.fields.EmailField`. **Сигнатура:** `EmailField(max_length=None, min_length=None, allow_blank=False)`. ## RegexField Текстовое представление, которое проверяет соответствие заданного значения определенному регулярному выражению. Соответствует `django.forms.fields.RegexField`. **Сигнатура:** `RegexField(regex, max_length=None, min_length=None, allow_blank=False)`. Обязательный аргумент `regex` может быть либо строкой, либо скомпилированным объектом регулярного выражения python. Для проверки используется `django.core.validators.RegexValidator` от Django. ## SlugField Поле `RegexField`, которое проверяет вводимые данные на соответствие шаблону `[a-zA-Z0-9_-]+`. Соответствует `django.db.models.fields.SlugField`. **Сигнатура:** `SlugField(max_length=50, min_length=None, allow_blank=False)`. ## URLField Поле `RegexField`, которое проверяет вводимые данные на соответствие шаблону URL. Ожидаются полностью определенные URL вида `http:///`. Соответствует `django.db.models.fields.URLField`. Для проверки используется `django.core.validators.URLValidator`. **Сигнатура:** `URLField(max_length=200, min_length=None, allow_blank=False)`. ## UUIDField Поле, которое гарантирует, что вводимые данные являются правильной строкой UUID. Метод `to_internal_value` возвращает экземпляр `uuid.UUID`. На выходе поле вернет строку в каноническом дефисном формате, например: ``` "de305d54-75b4-431b-adb2-eb6b9e546013" ``` **Сигнатура:** `UUIDField(format='hex_verbose')`. * `format`: Определяет формат представления значения uuid - `'hex_verbose'` - Каноническое шестнадцатеричное представление, включая дефисы: `"5ce0e9a5-5ffa-654b-cee0-1238041fb31a"`. - `'hex'` - Компактное шестнадцатеричное представление UUID, не включая дефисы: `"5ce0e9a55ffa654bcee01238041fb31a"`. - `'int'` - 128-битное целочисленное представление UUID: `"123456789012312313134124512351145145114"`. - `'urn'` - RFC 4122 URN-представление UUID: `'urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a'`. Изменение параметров `формата` влияет только на значения представления. Все форматы принимаются функцией `to_internal_value`. ## FilePathField Поле, выбор которого ограничен именами файлов в определенном каталоге файловой системы Соответствует `django.forms.fields.FilePathField`. **Сигнатура:** `FilePathField(path, match=None, recursive=False, allow_files=True, allow_folders=False, required=None, **kwargs)`. * `path` - Абсолютный путь файловой системы к директории, из которой это поле FilePathField должно получить свой выбор. * `match` - Регулярное выражение в виде строки, которое FilePathField будет использовать для фильтрации имен файлов. * `recursive` - Указывает, должны ли включаться все подкаталоги пути. По умолчанию `False`. * `allow_files` - Указывает, должны ли включаться файлы в указанном месте. По умолчанию `True`. Либо это, либо `allow_folders` должно быть `True`. * `allow_folders` - Указывает, должны ли включаться папки в указанном месте. По умолчанию `False`. Либо это, либо `allow_files` должно быть `True`. ## IPAddressField Поле, гарантирующее, что вводимые данные являются действительной строкой IPv4 или IPv6. Соответствует `django.forms.fields.IPAddressField` и `django.forms.fields.GenericIPAddressField`. **Сигнатура**: `IPAddressField(protocol='both', unpack_ipv4=False, **options)`. * `protocol` Ограничивает допустимые входы указанным протоколом. Принимаемые значения: `'both'` (по умолчанию), `'IPv4'` или `'IPv6'`. Соответствие не зависит от регистра. * `unpack_ipv4` Распаковывает сопоставленные IPv4-адреса, например `::ffff:192.0.2.1`. Если эта опция включена, то адрес будет распакован в `192.0.2.1`. По умолчанию отключена. Может использоваться только в том случае, если для протокола установлено значение `'both'`. --- # Числовые поля ## IntegerField Целочисленное представление. Соответствует `django.db.models.fields.IntegerField`, `django.db.models.fields.SmallIntegerField`, `django.db.models.fields.PositiveIntegerField` и `django.db.models.fields.PositiveSmallIntegerField`. **Сигнатура**: `IntegerField(max_value=None, min_value=None)`. * `max_value` Проверяет, что предоставленное число не больше этого значения. * `min_value` Проверяет, что предоставленное число не меньше этого значения. ## BigIntegerField Представление большого целого числа. Соответствует `django.db.models.fields.BigIntegerField`. **Сигнатура**: `BigIntegerField(max_value=None, min_value=None, coerce_to_string=None)` * `max_value` Проверяет, что предоставленное число не больше этого значения. * `min_value` Проверяет, что предоставленное число не меньше этого значения. * `coerce_to_string` Устанавливается в `True`, если для представления должны возвращаться строковые значения, или в `False`, если должны возвращаться объекты `BigInteger`. По умолчанию используется то же значение, что и для ключа настройки `COERCE_BIGINT_TO_STRING`, которое будет `False`, если не переопределено. Если сериализатор возвращает объекты `BigInteger`, то окончательный формат вывода будет определяться рендерером. ## FloatField Представление числа с плавающей точкой. Соответствует `django.db.models.fields.FloatField`. **Сигнатура**: `FloatField(max_value=None, min_value=None)`. * `max_value` Проверяет, что предоставленное число не больше этого значения. * `min_value` Проверяет, что предоставленное число не меньше этого значения. ## DecimalField Десятичное представление, представленное в Python экземпляром `Decimal`. Соответствует `django.db.models.fields.DecimalField`. **Сигнатура**: `DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None)`. * `max_digits` Максимальное количество цифр, допустимое в числе. Оно должно быть либо `None`, либо целым числом, большим или равным `decimal_places`. * `decimal_places` Количество десятичных знаков, которые следует хранить в числе. * `coerce_to_string` Установите значение `True`, если для представления должны быть возвращены строковые значения, или `False`, если должны быть возвращены объекты `Decimal`. По умолчанию имеет то же значение, что и ключ настроек `COERCE_DECIMAL_TO_STRING`, который будет `True`, если его не переопределить. Если сериализатор возвращает `Decimal` объекты, то окончательный формат вывода будет определяться рендерером. Обратите внимание, что установка `localize` заставит значение быть `True`. * `max_value` Проверяет, что предоставленное число не больше этого значения. Должно быть целым числом или объектом `Decimal`. * `min_value` Проверяет, что предоставленное число не меньше этого значения. Должно быть целым числом или объектом `Decimal`. * `localize` Установите значение `True`, чтобы включить локализацию ввода и вывода на основе текущей локали. Это также заставит `coerce_to_string` принять значение `True`. По умолчанию установлено значение `False`. Обратите внимание, что форматирование данных будет включено, если вы установили `USE_L10N=True` в вашем файле настроек. * `rounding` Устанавливает режим округления, используемый при квантовании с заданной точностью. Допустимые значения: [`decimal` module rounding modes](https://docs.python.org/3/library/decimal.html#rounding-modes). По умолчанию `None`. * `normalize_output` Нормализует десятичное значение при сериализации. При этом удаляются все нули в конце строки и точность значения изменяется до минимально необходимой, чтобы можно было представить значение без потери данных. По умолчанию имеет значение `False`. #### Пример использования Для проверки чисел до 999 с разрешением 2 знака после запятой можно использовать: ```python serializers.DecimalField(max_digits=5, decimal_places=2) ``` А также для проверки чисел вплоть до миллиарда с разрешением 10 знаков после запятой: ```python serializers.DecimalField(max_digits=19, decimal_places=10) ``` --- # Поля даты и времени ## DateTimeField Представление даты и времени. Соответствует `django.db.models.fields.DateTimeField`. **Сигнатура:** `DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None, default_timezone=None)`. * `format` - Строка, представляющая формат вывода. Если он не указан, то по умолчанию принимает значение, равное ключу настроек `DATETIME_FORMAT`, который будет `'iso-8601'`, если он не установлен. Установка в строку формата указывает на то, что возвращаемые значения `to_representation` должны быть принудительно приведены к строковому виду. Строки формата описаны ниже. Установка этого значения в `None` указывает, что `to_representation` должен возвращать объекты Python `datetime`. В этом случае кодировка времени будет определяться рендерером. * `input_formats` - Список строк, представляющих входные форматы, которые могут быть использованы для разбора даты. Если он не указан, будет использоваться настройка `DATETIME_INPUT_FORMATS`, которая по умолчанию принимает значение `['iso-8601']`. * `default_timezone` - Подкласс `tzinfo` (`zoneinfo` или `pytz`), представляющий часовой пояс. Если он не указан и включен параметр `USE_TZ`, то по умолчанию используется [текущий часовой пояс](https://docs.djangoproject.com/en/stable/topics/i18n/timezones/#default-time-zone-and-current-time-zone). Если `USE_TZ` отключена, то объекты datetime будут наивными. #### Строки формата `DateTimeField`. Строки формата могут быть либо [Python strftime formats](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior), которые явно указывают формат, либо специальной строкой `'iso-8601'`, которая указывает на то, что следует использовать значения в стиле [ISO 8601](https://www.w3.org/TR/NOTE-datetime). (Например, `'2013-01-29T12:34:56.000000Z'`) Если для формата используется значение `None`, то объекты `datetime` будут возвращаться методом `to_representation`, а окончательное представление на выходе будет определяться классом renderer. #### Поля модели `auto_now` и `auto_now_add`. При использовании `ModelSerializer` или `HyperlinkedModelSerializer` обратите внимание, что любые поля модели с `auto_now=True` или `auto_now_add=True` будут использовать поля сериализатора, которые по умолчанию имеют значение `read_only=True`. Если вы хотите переопределить это поведение, вам нужно будет явно объявить `DateTimeField` в сериализаторе. Например: ```python class CommentSerializer(serializers.ModelSerializer): created = serializers.DateTimeField() class Meta: model = Comment ``` ## DateField Представление даты. Соответствует `django.db.models.fields.DateField`. **Сигнатура:** `DateField(format=api_settings.DATE_FORMAT, input_formats=None)`. * `format` - Строка, представляющая формат вывода. Если он не указан, то по умолчанию принимает значение, равное ключу настроек `DATE_FORMAT`, который будет `'iso-8601'`, если он не установлен. Установка в строку формата указывает на то, что возвращаемые значения `to_representation` должны быть преобразованы в строковый вывод. Строки формата описаны ниже. Установка этого значения в `None` указывает, что `to_representation` должен возвращать объекты Python `date`. В этом случае кодировка даты будет определяться рендерером. * `input_formats` - Список строк, представляющих входные форматы, которые могут быть использованы для разбора даты. Если он не указан, будет использоваться настройка `DATE_INPUT_FORMATS`, которая по умолчанию принимает значение `['iso-8601']`. #### Строки формата `DateField`. Строки формата могут быть либо [Python strftime formats](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior), которые явно указывают формат, либо специальной строкой `'iso-8601'`, которая указывает, что должны использоваться даты в стиле [ISO 8601](https://www.w3.org/TR/NOTE-datetime). (например, `'2013-01-29'`) ## TimeField Представление времени. Соответствует `django.db.models.fields.TimeField`. **Сигнатура:** `TimeField(format=api_settings.TIME_FORMAT, input_formats=None)`. * `format` - строка, представляющая формат вывода. Если он не указан, то по умолчанию принимает значение, равное ключу настроек `TIME_FORMAT`, который будет `'iso-8601'`, если он не установлен. Установка в строку формата указывает на то, что возвращаемые значения `to_representation` должны быть принудительно выведены в строку. Строки формата описаны ниже. Установка этого значения в `None` указывает, что `to_representation` должен возвращать объекты Python `time`. В этом случае кодировка времени будет определяться рендерером. * `input_formats` - Список строк, представляющих входные форматы, которые могут быть использованы для разбора даты. Если он не указан, будет использоваться настройка `TIME_INPUT_FORMATS`, которая по умолчанию принимает значение `['iso-8601']`. #### Строки формата `TimeField`. Строки формата могут быть либо [Python strftime formats](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior), которые явно указывают формат, либо специальной строкой `'iso-8601'`, которая указывает, что должно использоваться время в стиле [ISO 8601](https://www.w3.org/TR/NOTE-datetime). (например, `'12:34:56.000000'`) ## DurationField Представление длительности. Соответствует `django.db.models.fields.DurationField`. В `validated_data` для этих полей будет содержаться экземпляр `datetime.timedelta`. **Сигнатура:** `DurationField(format=api_settings.DURATION_FORMAT, max_value=None, min_value=None)`. * `format` - строка, представляющая формат вывода. Если не указано, по умолчанию используется то же значение, что и в ключе настроек `DURATION_FORMAT`, которое будет `'django'`, если не установлено. Форматы описаны ниже. Установка этого значения в `None` указывает, что объекты Python `timedelta` должны возвращаться `to_representation`. В этом случае кодировка даты будет определяться рендерером. * `max_value` Проверяет, что предоставленная продолжительность не больше этого значения. * `min_value` Проверяет, что предоставленная продолжительность не меньше этого значения. #### Форматы `DurationField` Формат может быть либо специальной строкой `'iso-8601'`, которая указывает, что должны использоваться интервалы в стиле [ISO 8601](https://www.w3.org/TR/NOTE-datetime) (например, `'P4DT1H15M20S'`), либо `'django'`, что указывает на то, что должен использоваться формат интервала Django `'[DD] [HH:[MM:]]ss[.uuuuuu]'` (например, `'4 1:15:20'`). --- # Поля выбора ## ChoiceField Поле, которое может принимать значение из ограниченного набора вариантов. Используется `ModelSerializer` для автоматической генерации полей, если соответствующее поле модели содержит аргумент `choices=...`. **Сигнатура:** `ChoiceField(choices)`. * `choices` - Список допустимых значений, или список кортежей `(key, display_name)`. * `allow_blank` - Если установлено значение `True`, то пустая строка будет считаться допустимым значением. Если установлено значение `False`, то пустая строка будет считаться недопустимой и вызовет ошибку валидации. По умолчанию `False`. * `html_cutoff` - Если установлено, то это будет максимальное количество вариантов, которые будут отображаться в выпадающем списке HTML select. Может использоваться для того, чтобы автоматически генерируемые поля выбора с очень большим количеством возможных вариантов выбора не мешали отрисовке шаблона. По умолчанию `None`. * `html_cutoff_text` - Если установлено, то будет отображаться текстовый индикатор, если максимальное количество элементов было отсечено в выпадающем списке HTML select. По умолчанию `"More than {count} items..."`. Оба параметра `allow_blank` и `allow_null` являются допустимыми для `ChoiceField`, хотя настоятельно рекомендуется использовать только один из них, а не оба. `allow_blank` следует предпочесть для текстовых вариантов, а `allow_null` - для числовых или других нетекстовых вариантов. ## MultipleChoiceField Поле, которое может принимать нулевое, одно или множество значений, выбираемых из ограниченного набора вариантов. Принимает один обязательный аргумент. `to_internal_value` возвращает `set`, содержащий выбранные значения. **Сигнатура:** `MultipleChoiceField(choices)`. * `choices` - Список допустимых значений, или список кортежей `(key, display_name)`. * `allow_blank` - Если установлено значение `True`, то пустая строка будет считаться допустимым значением. Если установлено значение `False`, то пустая строка будет считаться недопустимой и вызовет ошибку валидации. По умолчанию `False`. * `html_cutoff` - Если установлено, то это будет максимальное количество вариантов, которые будут отображаться в выпадающем списке HTML select. Может использоваться для того, чтобы автоматически генерируемые поля выбора с очень большим количеством возможных вариантов выбора не мешали отрисовке шаблона. По умолчанию `None`. * `html_cutoff_text` - Если установлено, то будет отображаться текстовый индикатор, если максимальное количество элементов было отсечено в выпадающем списке HTML select. По умолчанию `"More than {count} items..."`. Как и в случае с `ChoiceField`, оба параметра `allow_blank` и `allow_null` являются допустимыми, хотя настоятельно рекомендуется использовать только один из них, а не оба. `allow_blank` следует предпочесть для текстовых вариантов, а `allow_null` - для числовых или других нетекстовых вариантов. --- # Поля для загрузки файлов #### Парсеры и загрузка файлов. Классы `FileField` и `ImageField` подходят только для использования с `MultiPartParser` или `FileUploadParser`. Большинство парсеров, например, JSON, не поддерживают загрузку файлов. Для работы с загруженными файлами в Django используются штатные [FILE_UPLOAD_HANDLERS](https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS). ## FileField Представление файла. Выполняет стандартную для Django проверку `FileField`. Соответствует `django.forms.fields.FileField`. **Сигнатура:** `FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)`. * `max_length` - Указывает максимальную длину имени файла. * `allow_empty_file` - Указывает, разрешены ли пустые файлы. * `use_url` - Если установлено значение `True`, то для выходного представления будут использоваться строковые значения URL. Если установлено значение `False`, то для вывода будут использоваться строковые значения имен файлов. По умолчанию принимает значение ключа настроек `UPLOADED_FILES_USE_URL`, которое равно `True`, если не установлено иное. ## ImageField Представление изображения. Проверяет соответствие содержимого загруженного файла известному формату изображения. Соответствует `django.forms.fields.ImageField`. **Сигнатура:** `ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)`. * `max_length` - Указывает максимальную длину имени файла. * `allow_empty_file` - Указывает, разрешены ли пустые файлы. * `use_url` - Если установлено значение `True`, то для выходного представления будут использоваться строковые значения URL. Если установлено значение `False`, то для вывода будут использоваться строковые значения имен файлов. По умолчанию принимает значение ключа настроек `UPLOADED_FILES_USE_URL`, которое равно `True`, если не установлено иное. Требуется либо пакет `Pillow`, либо пакет `PIL`. Рекомендуется использовать пакет `Pillow`, так как пакет `PIL` больше активно не поддерживается. --- # Составные поля ## ListField Класс поля, который проверяет список объектов. **Сигнатура**: `ListField(child=, allow_empty=True, min_length=None, max_length=None)`. * `child` - Экземпляр поля, который должен использоваться для проверки объектов в списке. Если этот аргумент не указан, то объекты в списке не будут проверяться. * `allow_empty` - Указывает, разрешены ли пустые списки. * `min_length` - Проверяет, что список содержит не менее этого количества элементов. * `max_length` - Проверяет, что список содержит не более этого количества элементов. Например, для проверки списка целых чисел вы можете использовать что-то вроде следующего: ```python scores = serializers.ListField( child=serializers.IntegerField(min_value=0, max_value=100) ) ``` Класс `ListField` также поддерживает декларативный стиль, который позволяет писать многократно используемые классы полей списков. ```python class StringListField(serializers.ListField): child = serializers.CharField() ``` Теперь мы можем повторно использовать наш пользовательский класс `StringListField` во всем нашем приложении, без необходимости указывать для него аргумент `child`. ## DictField Класс поля, который проверяет словарь объектов. Ключи в `DictField` всегда предполагаются как строковые значения. **Сигнатура**: `DictField(child=, allow_empty=True)`. * `child` - Экземпляр поля, который должен использоваться для проверки значений в словаре. Если этот аргумент не указан, то значения в отображении не будут проверяться. * `allow_empty` - Указывает, разрешены ли пустые словари. Например, чтобы создать поле, которое проверяет сопоставление строк со строками, вы должны написать что-то вроде этого: ```python document = DictField(child=CharField()) ``` Вы также можете использовать декларативный стиль, как в случае с `ListField`. Например: ```python class DocumentField(DictField): child = CharField() ``` ## HStoreField Предварительно настроенное `DictField`, совместимое с `HStoreField` от Django для postgres. **Сигнатура**: `HStoreField(child=, allow_empty=True)`. * `child` - экземпляр поля, который используется для проверки значений в словаре. По умолчанию дочернее поле принимает как пустые строки, так и нулевые значения. * `allow_empty` - Указывает, разрешены ли пустые словари. Обратите внимание, что дочернее поле **должно** быть экземпляром `CharField`, так как расширение hstore хранит значения в виде строк. ## JSONField Класс поля, который проверяет, что входящая структура данных состоит из корректных примитивов JSON. В альтернативном двоичном режиме он представляет и проверяет двоичные строки, закодированные в JSON. **Сигнатура**: `JSONField(binary, encoder)`. * `binary` - Если установлено значение `True`, то поле будет выводить и проверять строку, закодированную в JSON, а не примитивную структуру данных. По умолчанию `False`. * `encoder` - Используйте этот JSON-кодер для сериализации входного объекта. По умолчанию `None`. --- # Разные поля ## ReadOnlyField Класс поля, который просто возвращает значение поля без изменений. Это поле используется по умолчанию в `ModelSerializer` при включении имен полей, относящихся к атрибуту, а не к полю модели. **Сигнатура**: `ReadOnlyField()`. Например, если бы `has_expired` было свойством модели `Account`, то следующий сериализатор автоматически сгенерировал бы его как `ReadOnlyField`: ```python class AccountSerializer(serializers.ModelSerializer): class Meta: model = Account fields = ['id', 'account_name', 'has_expired'] ``` ## HiddenField Класс поля, которое не принимает значение на основе пользовательского ввода, а берет его из значения по умолчанию или вызываемого объекта. **Сигнатура**: `HiddenField()`. Например, чтобы включить поле, которое всегда предоставляет текущее время как часть проверяемых сериализатором данных, вы должны использовать следующее: ```python modified = serializers.HiddenField(default=timezone.now) ``` Класс `HiddenField` обычно нужен только в том случае, если у вас есть валидация, которая должна выполняться на основе некоторых предварительно предоставленных значений полей, но вы не хотите открывать все эти поля конечному пользователю. Другие примеры по `HiddenField` смотрите в документации [validators](validators.md). --- **Примечание:** `HiddenField()` не появляется в сериализаторе `partial=True` (при выполнении запроса `PATCH`). --- ## ModelField Общее поле, которое может быть привязано к любому произвольному полю модели. Класс `ModelField` делегирует задачу сериализации/десериализации связанному с ним полю модели. Это поле можно использовать для создания полей сериализатора для пользовательских полей модели, без необходимости создавать новое пользовательское поле сериализатора. Это поле используется `ModelSerializer` для соответствия классам полей пользовательской модели. **Сигнатура:** `ModelField(model_field=)`. Класс `ModelField` обычно предназначен для внутреннего использования, но при необходимости может быть использован в вашем API. Чтобы правильно инстанцировать `ModelField`, ему должно быть передано поле, привязанное к инстанцированной модели. Например: `ModelField(model_field=MyModel()._meta.get_field('custom_field'))`. ## SerializerMethodField Это поле, доступное только для чтения. Оно получает свое значение путем вызова метода класса сериализатора, к которому оно присоединено. Его можно использовать для добавления любых данных в сериализованное представление вашего объекта. **Сигнатура**: `SerializerMethodField(method_name=None)`. * `method_name` - Имя метода в сериализаторе, который будет вызван. Если оно не включено, то по умолчанию используется `get_<имя_поля>`. Метод сериализатора, на который ссылается аргумент `имя_метода`, должен принимать единственный аргумент (в дополнение к `self`), которым является сериализуемый объект. Он должен возвращать все, что вы хотите включить в сериализованное представление объекта. Например: ```python from django.contrib.auth.models import User from django.utils.timezone import now from rest_framework import serializers class UserSerializer(serializers.ModelSerializer): days_since_joined = serializers.SerializerMethodField() class Meta: model = User fields = '__all__' def get_days_since_joined(self, obj): return (now() - obj.date_joined).days ``` --- # Пользовательские поля Если вы хотите создать пользовательское поле, вам нужно создать подкласс `Field`, а затем переопределить один или оба метода `.to_representation()` и `.to_internal_value()`. Эти два метода используются для преобразования между исходным типом данных и примитивным, сериализуемым типом данных. Примитивными типами данных обычно являются число, строка, булево значение, `date`/`time`/`datetime` или `None`. Также это может быть любой список или словарь, содержащий только другие примитивные объекты. Могут поддерживаться и другие типы, в зависимости от используемого рендерера. Метод `.to_representation()` вызывается для преобразования исходного типа данных в примитивный, сериализуемый тип данных. Метод `.to_internal_value()` вызывается для восстановления примитивного типа данных в его внутреннее python-представление. Этот метод должен вызвать ошибку `serializers.ValidationError`, если данные недействительны. ## Примеры ### Базовое пользовательское поле Давайте рассмотрим пример сериализации класса, представляющего значение цвета RGB: ```python class Color: """ A color represented in the RGB colorspace. """ def __init__(self, red, green, blue): assert(red >= 0 and green >= 0 and blue >= 0) assert(red < 256 and green < 256 and blue < 256) self.red, self.green, self.blue = red, green, blue class ColorField(serializers.Field): """ Color objects are serialized into 'rgb(#, #, #)' notation. """ def to_representation(self, value): return "rgb(%d, %d, %d)" % (value.red, value.green, value.blue) def to_internal_value(self, data): data = data.strip('rgb(').rstrip(')') red, green, blue = [int(col) for col in data.split(',')] return Color(red, green, blue) ``` По умолчанию значения полей рассматриваются как сопоставление с атрибутом объекта. Если вам нужно настроить доступ к значению поля и его установку, вам нужно переопределить `.get_attribute()` и/или `.get_value()`. В качестве примера создадим поле, которое может быть использовано для представления имени класса сериализуемого объекта: ```python class ClassNameField(serializers.Field): def get_attribute(self, instance): # We pass the object instance onto `to_representation`, # not just the field attribute. return instance def to_representation(self, value): """ Serialize the value's class name. """ return value.__class__.__name__ ``` ### Вызов ошибок проверки Наш класс `ColorField`, описанный выше, в настоящее время не выполняет никакой проверки данных. Чтобы указать на недопустимые данные, мы должны вызвать ошибку `serializers.ValidationError`, как показано ниже: ```python def to_internal_value(self, data): if not isinstance(data, str): msg = 'Incorrect type. Expected a string, but got %s' raise ValidationError(msg % type(data).__name__) if not re.match(r'^rgb\([0-9]+,[0-9]+,[0-9]+\)$', data): raise ValidationError('Incorrect format. Expected `rgb(#,#,#)`.') data = data.strip('rgb(').rstrip(')') red, green, blue = [int(col) for col in data.split(',')] if any([col > 255 or col < 0 for col in (red, green, blue)]): raise ValidationError('Value out of range. Must be between 0 and 255.') return Color(red, green, blue) ``` Метод `.fail()` - это ярлык для вызова `ValidationError`, который принимает строку сообщения из словаря `error_messages`. Например: ```python default_error_messages = { 'incorrect_type': 'Incorrect type. Expected a string, but got {input_type}', 'incorrect_format': 'Incorrect format. Expected `rgb(#,#,#)`.', 'out_of_range': 'Value out of range. Must be between 0 and 255.' } def to_internal_value(self, data): if not isinstance(data, str): self.fail('incorrect_type', input_type=type(data).__name__) if not re.match(r'^rgb\([0-9]+,[0-9]+,[0-9]+\)$', data): self.fail('incorrect_format') data = data.strip('rgb(').rstrip(')') red, green, blue = [int(col) for col in data.split(',')] if any([col > 255 or col < 0 for col in (red, green, blue)]): self.fail('out_of_range') return Color(red, green, blue) ``` Этот стиль делает сообщения об ошибках более чистыми и отделенными от кода, поэтому его следует предпочесть. ### Использование `source='*'` Здесь мы рассмотрим пример _плоской_ модели `DataPoint` с атрибутами `x_coordinate` и `y_coordinate`. ```python class DataPoint(models.Model): label = models.CharField(max_length=50) x_coordinate = models.SmallIntegerField() y_coordinate = models.SmallIntegerField() ``` Используя пользовательское поле и `source='*'`, мы можем предоставить вложенное представление пары координат: ```python class CoordinateField(serializers.Field): def to_representation(self, value): ret = { "x": value.x_coordinate, "y": value.y_coordinate } return ret def to_internal_value(self, data): ret = { "x_coordinate": data["x"], "y_coordinate": data["y"], } return ret class DataPointSerializer(serializers.ModelSerializer): coordinates = CoordinateField(source='*') class Meta: model = DataPoint fields = ['label', 'coordinates'] ``` Обратите внимание, что в этом примере не предусмотрена валидация. Отчасти по этой причине в реальном проекте вложенность координат может быть лучше обработана с помощью вложенного сериализатора используя `source='*'`, с двумя экземплярами `IntegerField`, каждый из которых имеет свой собственный `source` указывающий на соответствующее поле. Ключевыми моментами из этого примера являются следующие: * `to_representation` передается весь объект `DataPoint`, который должен быть отображен в нужный вывод. ```python >>> instance = DataPoint(label='Example', x_coordinate=1, y_coordinate=2) >>> out_serializer = DataPointSerializer(instance) >>> out_serializer.data ReturnDict([('label', 'Example'), ('coordinates', {'x': 1, 'y': 2})]) ``` * Если только наше поле не будет доступно только для чтения, `to_internal_value` должно возвращать дикту, подходящую для обновления целевого объекта. При использовании `source='*'`, возврат из `to_internal_value` будет обновлять корневой словарь данных, а не один ключ. ```python >>> data = { ... "label": "Second Example", ... "coordinates": { ... "x": 3, ... "y": 4, ... } ... } >>> in_serializer = DataPointSerializer(data=data) >>> in_serializer.is_valid() True >>> in_serializer.validated_data OrderedDict([('label', 'Second Example'), ('y_coordinate', 4), ('x_coordinate', 3)]) ``` Для полноты картины повторим то же самое, но с использованием вложенного сериализатора, предложенного выше: ```python class NestedCoordinateSerializer(serializers.Serializer): x = serializers.IntegerField(source='x_coordinate') y = serializers.IntegerField(source='y_coordinate') class DataPointSerializer(serializers.ModelSerializer): coordinates = NestedCoordinateSerializer(source='*') class Meta: model = DataPoint fields = ['label', 'coordinates'] ``` Здесь отображение между парами атрибутов цели и источника (`x` и `x_coordinate`, `y` и `y_coordinate`) обрабатывается в объявлениях `IntegerField`. Это наш `NestedCoordinateSerializer`, который принимает `source='*'`. Наш новый `DataPointSerializer` демонстрирует то же поведение, что и подход с пользовательскими полями. Сериализация: ```python >>> out_serializer = DataPointSerializer(instance) >>> out_serializer.data ReturnDict([('label', 'testing'), ('coordinates', OrderedDict([('x', 1), ('y', 2)]))]) ``` Десериализация: ```python >>> in_serializer = DataPointSerializer(data=data) >>> in_serializer.is_valid() True >>> in_serializer.validated_data OrderedDict([('label', 'still testing'), ('x_coordinate', 3), ('y_coordinate', 4)]) ``` Но мы также получаем встроенную валидацию бесплатно: ```python >>> invalid_data = { ... "label": "still testing", ... "coordinates": { ... "x": 'a', ... "y": 'b', ... } ... } >>> invalid_serializer = DataPointSerializer(data=invalid_data) >>> invalid_serializer.is_valid() False >>> invalid_serializer.errors ReturnDict([('coordinates', {'x': ['A valid integer is required.'], 'y': ['A valid integer is required.']})]) ``` По этой причине подход с использованием вложенного сериализатора следует попробовать в первую очередь. Вы будете использовать подход с пользовательскими полями, когда вложенный сериализатор станет невыполнимым или слишком сложным. # Пакеты сторонних производителей Также доступны следующие пакеты сторонних производителей. ## Составные поля DRF Пакет [drf-compound-fields](https://drf-compound-fields.readthedocs.io) предоставляет "составные" поля сериализатора, такие как списки простых значений, которые могут быть описаны другими полями, а не сериализаторами с опцией `many=True`. Также предоставляются поля для типизированных словарей и значений, которые могут быть либо определенным типом, либо списком элементов этого типа. ## Дополнительные поля DRF Пакет [drf-extra-fields](https://github.com/Hipo/drf-extra-fields) предоставляет дополнительные поля сериализатора для DRF, включая классы `Base64ImageField` и `PointField`. ## djangorestframework-recursive Пакет [djangorestframework-recursive](https://github.com/heywbj/django-rest-framework-recursive) предоставляет `RecursiveField` для сериализации и десериализации рекурсивных структур. ## django-rest-framework-gis Пакет [django-rest-framework-gis](https://github.com/djangonauts/django-rest-framework-gis) предоставляет географические дополнения для DRF, такие как поле `GeometryField` и сериализатор GeoJSON. ## django-rest-framework-hstore Пакет [django-rest-framework-hstore](https://github.com/djangonauts/django-rest-framework-hstore) предоставляет `HStoreField` для поддержки поля модели [django-hstore](https://github.com/djangonauts/django-hstore) `DictionaryField`. ================================================ FILE: api-guide/filtering.md ================================================ --- источник: - filters.py --- # Фильтрация > Корневой QuerySet, предоставляемый менеджером, описывает все объекты в таблице базы данных. Однако обычно требуется выбрать только подмножество из полного набора объектов. > > — [Django documentation](https://docs.djangoproject.com/en/stable/topics/db/queries/#retrieving-specific-objects-with-filters) По умолчанию общие списочные представления DRF возвращают весь `QuerySet` для менеджера модели. Часто требуется, чтобы API ограничивал количество элементов, возвращаемых `QuerySet`. Простейшим способом фильтрации `QuerySet` любого представления, подкласса `GenericAPIView`, является переопределение метода `.get_queryset()`. Переопределение этого метода позволяет настраивать `QuerySet`, возвращаемый представлением, различными способами. ## Фильтрация по текущему пользователю Возможно, потребуется отфильтровать `QuerySet`, чтобы возвращать только результаты, относящиеся к текущему аутентифицированному пользователю, сделавшему запрос. Это можно сделать с помощью фильтрации по значению `request.user`. Например: ```python from myapp.models import Purchase from myapp.serializers import PurchaseSerializer from rest_framework import generics class PurchaseList(generics.ListAPIView): serializer_class = PurchaseSerializer def get_queryset(self): """ This view should return a list of all the purchases for the currently authenticated user. """ user = self.request.user return Purchase.objects.filter(purchaser=user) ``` ## Фильтрация по URL Другой стиль фильтрации может включать ограничение `QuerySet` на основе некоторой части URL. Например, если в конфигурации URL содержится запись следующего вида: ```python re_path('^purchases/(?P.+)/$', PurchaseList.as_view()), ``` Затем вы можете написать представление, возвращающее `QuerySet` покупок, отфильтрованный по имени пользователя в части URL: ```python class PurchaseList(generics.ListAPIView): serializer_class = PurchaseSerializer def get_queryset(self): """ This view should return a list of all the purchases for the user as determined by the username portion of the URL. """ username = self.kwargs['username'] return Purchase.objects.filter(purchaser__username=username) ``` ## Фильтрация по параметрам запроса Следующим примером фильтрации исходного QuerySet может быть определение исходного QuerySet на основе параметров запроса в url. Мы можем переопределить `.get_queryset()` для работы с такими URL, как `http://example.com/api/purchases?username=denvercoder9`, и фильтровать QuerySet только в том случае, если в URL включен параметр `username`: ```python class PurchaseList(generics.ListAPIView): serializer_class = PurchaseSerializer def get_queryset(self): """ Optionally restricts the returned purchases to a given user, by filtering against a `username` query parameter in the URL. """ queryset = Purchase.objects.all() username = self.request.query_params.get('username') if username is not None: queryset = queryset.filter(purchaser__username=username) return queryset ``` --- # Общая фильтрация Помимо возможности переопределения стандартного QuerySet, DRF также включает поддержку общих бэкендов фильтрации, которые позволяют легко строить сложные поисковые запросы и фильтры. Общие фильтры также могут быть представлены в виде элементов управления HTML в API просмотра и API администрирования. ![Пример фильтра](https://github.com/encode/django-rest-framework/raw/main/docs/img/filter-controls.png) ## Настройка бэкендов фильтров Бэкенды фильтров по умолчанию могут быть заданы глобально, с помощью параметра `DEFAULT_FILTER_BACKENDS`. Например: ```python REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'] } ``` Вы также можете задать бэкенды фильтров для каждого вида или для каждого набора видов, используя класс `GenericAPIView`, основанный на представлениях. ```python import django_filters.rest_framework from django.contrib.auth.models import User from myapp.serializers import UserSerializer from rest_framework import generics class UserListView(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer filter_backends = [django_filters.rest_framework.DjangoFilterBackend] ``` ## Фильтрация и поиск объектов Обратите внимание, что если для представления настроен бэкэнд фильтрации, то он будет использоваться не только для фильтрации списочных представлений, но и для фильтрации `QuerySet`, используемых для возврата одного объекта. Например, если взять предыдущий пример и товар с идентификатором `4675`, то следующий URL будет либо возвращать соответствующий объект, либо возвращать ответ 404, в зависимости от того, были ли выполнены условия фильтрации для данного экземпляра товара: ```bash http://example.com/api/products/4675/?category=clothing&max_price=10.00 ``` ## Переопределение исходного QuerySet Обратите внимание, что можно использовать и переопределенный `.get_queryset()`, и общую фильтрацию, и все будет работать так, как ожидается. Например, если у `Product` есть отношение "многие-ко-многим" с `User`, названное `purchase`, вы можете написать представление следующим образом: ```python class PurchasedProductsList(generics.ListAPIView): """ Return a list of all the products that the authenticated user has ever purchased, with optional filtering. """ model = Product serializer_class = ProductSerializer filterset_class = ProductFilter def get_queryset(self): user = self.request.user return user.purchase_set.all() ``` --- # Руководство по API ## DjangoFilterBackend Библиотека [`django-filter`](https://django-filter.readthedocs.io/en/latest/index.html) включает класс `DjangoFilterBackend`, который поддерживает высоконастраиваемую фильтрацию полей для DRF. Чтобы использовать `DjangoFilterBackend`, сначала установите `django-filter`. ```bash pip install django-filter ``` Затем добавьте `'django_filters'` в `INSTALLED_APPS` Django: ```python INSTALLED_APPS = [ ... 'django_filters', ... ] ``` Теперь необходимо либо добавить бэкэнд фильтра в настройки: ```python REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'] } ``` Или добавить бэкэнд фильтрации в отдельный `View` или `ViewSet`. ```python from django_filters.rest_framework import DjangoFilterBackend class UserListView(generics.ListAPIView): ... filter_backends = [DjangoFilterBackend] ``` Если вам нужна простая фильтрация на основе равенства, вы можете установить атрибут `filterset_fields` для представления или набора представлений, перечислив набор полей, по которым вы хотите осуществлять фильтрацию. ```python class ProductList(generics.ListAPIView): queryset = Product.objects.all() serializer_class = ProductSerializer filter_backends = [DjangoFilterBackend] filterset_fields = ['category', 'in_stock'] ``` Это автоматически создаст класс `FilterSet` для заданных полей и позволит выполнять такие запросы, как: ```bash http://example.com/api/products?category=clothing&in_stock=True ``` Для более сложных требований к фильтрации можно указать класс `FilterSet`, который должен использоваться представлением. Более подробно о `FilterSet` можно прочитать в [документации django-filter](https://django-filter.readthedocs.io/en/latest/index.html). Также рекомендуется прочитать раздел [Интеграция DRF](https://django-filter.readthedocs.io/en/latest/guide/rest_framework.html). ## SearchFilter Класс `SearchFilter` поддерживает простой поиск по одному параметру запроса и основан на функциональности [поиска в админ-панели Django](https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields). При использовании в состав Web-интерфейса API будет входить элемент управления `SearchFilter`: ![Фильтр поиска](https://github.com/encode/django-rest-framework/raw/main/docs/img/search-filter.png) Класс `SearchFilter` будет применяться только в том случае, если у представления установлен атрибут `search_fields`. Атрибут `search_fields` должен представлять собой список имен полей текстового типа в модели, например `CharField` или `TextField`. ```python from rest_framework import filters class UserListView(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer filter_backends = [filters.SearchFilter] search_fields = ['username', 'email'] ``` Это позволит клиенту фильтровать элементы списка, задавая такие запросы, как: ```bash http://example.com/api/users?search=russell ``` Также можно выполнить связанный поиск по полю `ForeignKey` или `ManyToManyField` с помощью нотации двойного подчеркивания в API поиска: ```python search_fields = ['username', 'email', 'profile__profession'] ``` Для полей [JSONField](https://docs.djangoproject.com/en/stable/ref/models/fields/#django.db.models.JSONField) и [HStoreField](https://docs.djangoproject.com/en/stable/ref/contrib/postgres/fields/#hstorefield) можно осуществлять фильтрацию по вложенным значениям внутри структуры данных, используя ту же нотацию двойного подчеркивания: ```python search_fields = ['data__breed', 'data__owner__other_pets__0__name'] ``` По умолчанию в поиске используются частичные совпадения без учета регистра. Параметр `search` может содержать несколько условий поиска, которые должны быть разделены пробелами и/или запятыми. Если используется несколько условий поиска, то объекты будут возвращены в списке только при совпадении всех указанных условий. Поиск может содержать _цитируемые фразы_ с пробелами, каждая фраза рассматривается как один поисковый термин. Поведение поиска может быть задано путем добавления префикса к именам полей в `search_fields` одним из следующих символов (что эквивалентно добавлению `__` к полю): | Префикс | Поиск | | |---------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `^` | `istartswith` | Начинается с поиска. | | `=` | `iexact` | Точные совпадения. | | `$` | `iregex` | Regex-поиск. | | `@` | `search` | Полнотекстовый поиск (в настоящее время поддерживается только бэкенд Django [PostgreSQL](https://docs.djangoproject.com/en/stable/ref/contrib/postgres/search/)). | | None | `icontains` | Содержит поиск (по умолчанию). | Например: ```python search_fields = ['=username', '=email'] ``` По умолчанию параметр поиска называется `'search'`, но это можно переопределить с помощью параметра `SEARCH_PARAM` в секции настроек `REST_FRAMEWORK`. Для динамического изменения полей поиска в зависимости от содержимого запроса можно подклассифицировать `SearchFilter` и переопределить функцию `get_search_fields()`. Например, следующий подкласс будет искать по `title`, только если в запросе присутствует параметр запроса `title_only`: ```python from rest_framework import filters class CustomSearchFilter(filters.SearchFilter): def get_search_fields(self, view, request): if request.query_params.get('title_only'): return ['title'] return super().get_search_fields(view, request) ``` Более подробную информацию можно найти в [документации Django](https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields). --- ## OrderingFilter Класс `OrderingFilter` поддерживает простое упорядочивание результатов, управляемое параметрами запроса. ![Ordering Filter](https://github.com/encode/django-rest-framework/raw/main/docs/img/ordering-filter.png) По умолчанию параметр запроса называется `'ordering'`, но это можно переопределить с помощью параметра `ORDERING_PARAM` в секции настроек `REST_FRAMEWORK`. Например, чтобы упорядочить пользователей по имени пользователя: ```bash http://example.com/api/users?ordering=username ``` Клиент может задать и обратный порядок, добавив к имени поля префикс '-', например, так: ```bash http://example.com/api/users?ordering=-username ``` Также может быть задано несколько порядков: ```bash http://example.com/api/users?ordering=account,username ``` ### Указание того, какие поля могут быть использованы для упорядочивания Рекомендуется явно указывать, какие поля API должен разрешать в фильтре упорядочивания. Это можно сделать, установив атрибут `ordering_fields` на представлении, например, так: ```python class UserListView(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer filter_backends = [filters.OrderingFilter] ordering_fields = ['username', 'email'] ``` Это позволяет предотвратить непредвиденную утечку данных, например, разрешить пользователям заказывать по хэш-полю пароли или другие конфиденциальные данные. Если _не_ указать атрибут `orderering_fields` для представления, то класс фильтра будет по умолчанию позволять пользователю фильтровать по любым читаемым полям на сериализаторе, указанном атрибутом `serializer_class`. Если вы уверены, что используемый представлением `Queryset` не содержит конфиденциальных данных, вы также можете явно указать, что представление должно разрешать упорядочивание по _любому_ полю модели или агрегату `Queryset`, используя специальное значение `'__all__'`. ```python class BookingsListView(generics.ListAPIView): queryset = Booking.objects.all() serializer_class = BookingSerializer filter_backends = [filters.OrderingFilter] ordering_fields = '__all__' ``` ### Указание порядка по умолчанию Если у представления установлен атрибут `ordering`, то он будет использоваться в качестве упорядочивания по умолчанию. Обычно для этого используется параметр `order_by` в исходном `Queryset`, но использование параметра `ordering` в представлении позволяет указать порядок таким образом, что он может быть автоматически передан в качестве контекста в шаблон рендеринга. Это позволяет автоматически отображать заголовки столбцов по-разному, если они используются для упорядочивания результатов. ```python class UserListView(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer filter_backends = [filters.OrderingFilter] ordering_fields = ['username', 'email'] ordering = ['username'] ``` Атрибут `orderering` может быть как строкой, так и списком/кортежем строк. --- # Пользовательская общая фильтрация Вы также можете предоставить свой собственный бэкэнд фильтрации или написать устанавливаемое приложение для использования другими разработчиками. Для этого переопределите `BaseFilterBackend` и переопределите метод `.filter_queryset(self, request, queryset, view)`. Метод должен возвращать новый, отфильтрованный `QuerySet`. Помимо того, что клиенты могут выполнять поиск и фильтрацию, общие бэкенды фильтров могут быть полезны для ограничения того, какие объекты должны быть видны для каждого конкретного запроса или пользователя. ## Пример Например, может потребоваться ограничить доступ пользователей только к созданным ими объектам. ```python class IsOwnerFilterBackend(filters.BaseFilterBackend): """ Filter that only allows users to see their own objects. """ def filter_queryset(self, request, queryset, view): return queryset.filter(owner=request.user) ``` Мы могли бы добиться такого же поведения, переопределив `get_queryset()` в представлениях, но использование бэкенда фильтров позволяет более просто добавить это ограничение к нескольким представлениям или применить его ко всему API. ## Настройка интерфейса Общие фильтры также могут представлять интерфейс в Web-интерфейсе API. Для этого необходимо реализовать метод `to_html()`, который возвращает отрендеренное HTML-представление фильтра. Этот метод должен иметь следующую сигнатуру: `to_html(self, request, queryset, view)`. Метод должен возвращать отрендеренную HTML-строку. # Пакеты сторонних производителей Следующие пакеты сторонних производителей предоставляют дополнительные реализации фильтров. ## Django-rest-framework-filters [django-rest-framework-filters](https://github.com/philipn/django-rest-framework-filters) работает совместно с классом `DjangoFilterBackend` и позволяет легко создавать фильтры по отношениям, а также создавать несколько типов фильтров для поиска по заданному полю. ## Djangorestframework-word-filter [djangorestframework-word-filter](https://github.com/trollknurr/django-rest-framework-word-search-filter) разработан как альтернатива `filters.SearchFilter`, который будет искать полное слово в тексте, либо точное совпадение. ## Django-url-filter [django-url-filter](https://github.com/miki725/django-url-filter) предоставляет безопасный способ фильтрации данных по удобным для человека URL-адресам. Он работает очень похоже на сериализаторы и поля DRF в том смысле, что они могут быть вложенными, за исключением того, что они называются `filtersets` и `filters`. Это обеспечивает простой способ фильтрации связанных данных. Кроме того, эта библиотека является универсальной, поэтому ее можно использовать для фильтрации других источников данных, а не только Django `QuerySet`. ## drf-url-filters [drf-url-filter](https://github.com/manjitkumar/drf-url-filters) - это простое Django-приложение для применения фильтров к `Queryset` в `ModelViewSet` чистым, простым и настраиваемым способом. Оно также поддерживает валидацию входящих параметров запроса и их значений. Для валидации входящих параметров запроса используется красивый python-пакет `Voluptuous`. Самое приятное в `Voluptuous` то, что вы можете определить свои собственные валидации в соответствии с требованиями к параметрам запроса. ================================================ FILE: api-guide/format-suffixes.md ================================================ # Cуффиксы формата > В разделе 6.2.1 не говорится, что согласование содержания должно использоваться постоянно. > > - Рой Филдинг, [список рассылки REST discuss](http://tech.groups.yahoo.com/group/rest-discuss/message/5857) Общим шаблоном для веб-интерфейсов является использование расширений имен файлов в URL-адресах для предоставления конечной точки для данного типа носителя. Например, 'http://example.com/api/users.json' для предоставления представления JSON. Добавление шаблонов суффиксов формата к каждой отдельной записи в URLconf для вашего API чревато ошибками и не соответствует стандарту DRY, поэтому DRF предоставляет быстрый способ добавления этих шаблонов в URLConf. ## format_suffix_patterns **Сигнатура**: `format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None)` Возвращает список шаблонов URL, который включает шаблоны суффиксов формата, добавленные к каждому из предоставленных шаблонов URL. Аргументы: * **urlpatterns**: Обязательно. Список шаблонов URL. * **suffix_required**: Необязательно. Булево значение, указывающее, должны ли суффиксы в URL быть необязательными или обязательными. По умолчанию `False`, что означает, что суффиксы по умолчанию необязательны. * **allowed**: Необязательно. Список или кортеж допустимых суффиксов формата. Если не указан, будет использоваться шаблон суффикса формата. Пример: ```python from rest_framework.urlpatterns import format_suffix_patterns from blog import views urlpatterns = [ path('', views.apt_root), path('comments/', views.comment_list), path('comments//', views.comment_detail) ] urlpatterns = format_suffix_patterns(urlpatterns, allowed=['json', 'html']) ``` При использовании `format_suffix_patterns`, вы должны убедиться, что добавили именованный аргумент `'format'` в соответствующие представления. Например: ```python @api_view(['GET', 'POST']) def comment_list(request, format=None): # do stuff... ``` Или с помощью представлений, основанных на классах: ```python class CommentList(APIView): def get(self, request, format=None): # do stuff... def post(self, request, format=None): # do stuff... ``` Имя используемого именнованного аргумента можно изменить с помощью параметра `FORMAT_SUFFIX_KWARG`. Также обратите внимание, что `format_suffix_patterns` не поддерживает применение к шаблонам URL `include`. ### Использование с `i18n_patterns`. При использовании функции `i18n_patterns`, предоставляемой Django, а также `format_suffix_patterns` вы должны убедиться, что функция `i18n_patterns` применяется как конечная, или крайняя функция. Например: ```python urlpatterns = [ … ] urlpatterns = i18n_patterns( format_suffix_patterns(urlpatterns, allowed=['json', 'html']) ) ``` --- ## Форматы параметров запроса Альтернативой суффиксам формата является включение запрашиваемого формата в параметр запроса. DRF предоставляет этот параметр по умолчанию, и он используется в Web-интерфейсе API для переключения между различными доступными представлениями. Чтобы выбрать представление по его краткому формату, используйте параметр запроса `format`. Например: `http://example.com/organizations/?format=csv`. Имя этого параметра запроса можно изменить с помощью параметра `URL_FORMAT_OVERRIDE`. Установите значение `None`, чтобы отключить это поведение. --- ## Принимать заголовки против суффиксов формата Похоже, некоторые представители веб-сообщества считают, что расширения имен файлов не являются шаблоном RESTful, и что вместо них всегда следует использовать заголовки `HTTP Accept`. На самом деле это заблуждение. Например, возьмем следующую цитату Роя Филдинга, обсуждающего относительные достоинства индикаторов медиатипа параметров запроса по сравнению с индикаторами медиатипа расширений файлов: > Вот почему я всегда предпочитаю расширения. Ни тот, ни другой выбор не имеют никакого отношения к REST". > > - Рой Филдинг, [Список рассылки REST discuss](https://groups.yahoo.com/neo/groups/rest-discuss/conversations/topics/14844) В цитате не упоминаются заголовки Accept, но она ясно дает понять, что суффиксы формата следует считать приемлемым шаблоном. ================================================ FILE: api-guide/generic-views.md ================================================ # Общие представления > Общие представления в Django... были разработаны как кратчайший путь к общим шаблонам использования... Они берут определенные общие идиомы и паттерны, встречающиеся в разработке представлений, и абстрагируют их, чтобы вы могли быстро писать общие представления данных без необходимости повторяться. > > - [Документация Django](https://docs.djangoproject.com/en/stable/ref/class-based-views/#base-vs-generic-views) Одним из ключевых преимуществ представлений, основанных на классах, является то, что они позволяют составлять фрагменты многократно используемого поведения. DRF использует это преимущество, предоставляя ряд готовых представлений, которые обеспечивают часто используемые шаблоны. Типовые представления, предоставляемые DRF, позволяют быстро создавать представления API, которые тесно связаны с моделями вашей базы данных. Если типовые представления не удовлетворяют потребностям вашего API, вы можете перейти к использованию обычного класса `APIView` или повторно использовать миксины и базовые классы, используемые типовыми представлениями, для создания собственного набора многократно используемых типовых представлений. ## Примеры Обычно при использовании общих представлений вы переопределяете представление и устанавливаете несколько атрибутов класса. ```python from django.contrib.auth.models import User from myapp.serializers import UserSerializer from rest_framework import generics from rest_framework.permissions import IsAdminUser class UserList(generics.ListCreateAPIView): queryset = User.objects.all() serializer_class = UserSerializer permission_classes = [IsAdminUser] ``` Для более сложных случаев вы также можете захотеть переопределить различные методы класса представления. Например. ```python class UserList(generics.ListCreateAPIView): queryset = User.objects.all() serializer_class = UserSerializer permission_classes = [IsAdminUser] def list(self, request): # Note the use of `get_queryset()` instead of `self.queryset` queryset = self.get_queryset() serializer = UserSerializer(queryset, many=True) return Response(serializer.data) ``` Для очень простых случаев вы можете передать любые атрибуты класса с помощью метода `.as_view()`. Например, ваша URLconf может включать что-то вроде следующей записи: ```python path('users/', ListCreateAPIView.as_view(queryset=User.objects.all(), serializer_class=UserSerializer), name='user-list') ``` --- # API Reference ## GenericAPIView Этот класс расширяет класс `APIView` DRF, добавляя часто требуемое поведение для стандартных представлений списка и детализации. Каждое из конкретных типовых представлений создается путем объединения `GenericAPIView` с одним или несколькими классами-миксинами. ### Атрибуты **Основные настройки**: Следующие атрибуты управляют основным поведением представления. * `queryset` - Набор queryset, который должен использоваться для возврата объектов из этого представления. Как правило, вы должны либо установить этот атрибут, либо переопределить метод `get_queryset()`. Если вы переопределяете метод представления, важно вызвать `get_queryset()`, а не обращаться к этому свойству напрямую, так как `queryset` будет оценен один раз, и эти результаты будут кэшироваться для всех последующих запросов. * `serializer_class` - Класс сериализатора, который должен использоваться для проверки и десериализации входных данных, а также для сериализации выходных данных. Как правило, вы должны либо установить этот атрибут, либо переопределить метод `get_serializer_class()`. * `lookup_field` - Поле модели, которое должно использоваться для выполнения поиска объектов в отдельных экземплярах модели. По умолчанию используется значение `'pk'`. Обратите внимание, что при использовании API с гиперссылками вам нужно убедиться, что *и* представления API, *и* классы сериализатора устанавливают поля поиска, если вам нужно использовать пользовательское значение. * `lookup_url_kwarg` - Именованный аргумент URL, который должен использоваться для поиска объекта. URL conf должен включать именованный аргумент, соответствующий этому значению. Если значение не установлено, по умолчанию используется то же значение, что и `lookup_field`. **Пагинация**: Следующие атрибуты используются для управления пагинацией при использовании представлений списка. * `pagination_class` - Класс пагинации, который должен использоваться при пагинации результатов списка. По умолчанию имеет то же значение, что и параметр `DEFAULT_PAGINATION_CLASS`, который является `'rest_framework.pagination.PageNumberPagination'`. Установка `pagination_class=None` отключит пагинацию в этом представлении. **Фильтрация**: * `filter_backends` - Список классов бэкендов фильтра, которые должны использоваться для фильтрации набора запросов. По умолчанию имеет то же значение, что и параметр `DEFAULT_FILTER_BACKENDS`. ### Методы **Базовые методы**: #### `get_queryset(self)`. Возвращает набор запросов, который должен использоваться для представлений списка и который должен использоваться в качестве базы для поиска в детальных представлениях. По умолчанию возвращается кверисет, указанный атрибутом `queryset`. Этот метод всегда следует использовать вместо прямого обращения к `self.queryset`, поскольку `self.queryset` оценивается только один раз, и эти результаты кэшируются для всех последующих запросов. Может быть переопределена для обеспечения динамического поведения, например, возврата набора запросов, специфичного для пользователя, делающего запрос. Например: ```python def get_queryset(self): user = self.request.user return user.accounts.all() ``` --- **Примечание:** Если класс `serializer_class`, используемый в общем представлении, охватывает несколько отношений, что приводит к проблеме n+1, вы можете оптимизировать ваш набор запросов в этом методе, используя `select_related` и `prefetch_related`. Для получения дополнительной информации о проблеме n+1 и случаях использования упомянутых методов обратитесь к разделу related в [документации django](https://docs.djangoproject.com/en/stable/ref/models/querysets/#django.db.models.query.QuerySet.select_related). --- ### Избегание запросов N+1 При перечислении объектов (например, с помощью `ListAPIView` или `ModelViewSet`) сериализаторы могут вызвать паттерн запросов N+1, если доступ к связанным объектам осуществляется индивидуально для каждого элемента. Чтобы этого не произошло, оптимизируйте набор запросов в `get_queryset()` или установите атрибут класса `queryset` с помощью [`select_related()`](https://docs.djangoproject.com/en/stable/ref/models/querysets/#select-related) и [`prefetch_related()`](https://docs.djangoproject.com/en/stable/ref/models/querysets/#prefetch-related), в зависимости от типа отношения. **Для ForeignKey и OneToOneField**: Используйте `select_related()`, чтобы получить связанные объекты в одном запросе: ```python def get_queryset(self): return Order.objects.select_related("customer", "billing_address") ``` **Для обратных и many-to-many отношений**: Используйте `prefetch_related()`, чтобы эффективно загрузить коллекции связанных объектов: ```python def get_queryset(self): return Book.objects.prefetch_related("categories", "reviews__user") ``` **Для сложных наборов запросов с несколькими типами отношений**: ```python def get_queryset(self): return ( Order.objects .select_related("customer") .prefetch_related("items__product") ) ``` Эти оптимизации уменьшают повторный доступ к базе данных и улучшают производительность представлений списка. --- #### `get_object(self)`. Возвращает экземпляр объекта, который должен использоваться для детальных представлений. По умолчанию используется параметр `lookup_field` для фильтрации базового набора запросов. Может быть переопределена для обеспечения более сложного поведения, например, поиска объектов на основе более чем одного именованного аргумента URL. Например: ```python def get_object(self): queryset = self.get_queryset() filter = {} for field in self.multiple_lookup_fields: filter[field] = self.kwargs[field] obj = get_object_or_404(queryset, **filter) self.check_object_permissions(self.request, obj) return obj ``` Обратите внимание, что если ваш API не включает разрешения на уровне объекта, вы можете исключить `self.check_object_permissions`, и просто вернуть объект из поиска `get_object_or_404`. #### `filter_queryset(self, queryset)`. Получив набор запросов, отфильтруйте его с помощью тех бэкендов фильтрации, которые используются, и верните новый набор запросов. Например: ```python def filter_queryset(self, queryset): filter_backends = [CategoryFilter] if 'geo_route' in self.request.query_params: filter_backends = [GeoRouteFilter, CategoryFilter] elif 'geo_point' in self.request.query_params: filter_backends = [GeoPointFilter, CategoryFilter] for backend in list(filter_backends): queryset = backend().filter_queryset(self.request, queryset, view=self) return queryset ``` #### `get_serializer_class(self)`. Возвращает класс, который должен быть использован для сериализатора. По умолчанию возвращается атрибут `serializer_class`. Может быть переопределен для обеспечения динамического поведения, например, использования различных сериализаторов для операций чтения и записи, или предоставления различных сериализаторов различным типам пользователей. Например: ```python def get_serializer_class(self): if self.request.user.is_staff: return FullAccountSerializer return BasicAccountSerializer ``` **Хуки для сохранения и удаления**: Следующие методы предоставляются mixin-классами и обеспечивают легкое переопределение поведения сохранения или удаления объекта. * `perform_create(self, serializer)` - Вызывается `CreateModelMixin` при сохранении нового экземпляра объекта. * `perform_update(self, serializer)` - Вызывается `UpdateModelMixin` при сохранении существующего экземпляра объекта. * `perform_destroy(self, instance)` - Вызывается `DestroyModelMixin` при удалении экземпляра объекта. Эти крючки особенно полезны для установки атрибутов, которые подразумеваются в запросе, но не являются частью данных запроса. Например, вы можете установить атрибут объекта на основе пользователя запроса или на основе именованного аргумента URL. ```python def perform_create(self, serializer): serializer.save(user=self.request.user) ``` Эти точки переопределения также особенно полезны для добавления поведения, которое происходит до или после сохранения объекта, например, отправки подтверждения по электронной почте или регистрации обновления. ```python def perform_update(self, serializer): instance = serializer.save() send_email_confirmation(user=self.request.user, modified=instance) ``` Вы также можете использовать эти крючки для обеспечения дополнительной проверки, вызывая `ValidationError()`. Это может быть полезно, если вам нужно применить логику валидации в момент сохранения базы данных. Например: ```python def perform_create(self, serializer): queryset = SignupRequest.objects.filter(user=self.request.user) if queryset.exists(): raise ValidationError('You have already signed up') serializer.save(user=self.request.user) ``` **Другие методы**: Обычно вам не нужно переопределять следующие методы, хотя вам может понадобиться обращаться к ним, если вы пишете пользовательские представления, используя `GenericAPIView`. * `get_serializer_context(self)` - Возвращает словарь, содержащий любой дополнительный контекст, который должен быть предоставлен сериализатору. По умолчанию включает ключи `'request'`, `'view'` и `'format'`. * `get_serializer(self, instance=None, data=None, many=False, partial=False)` - Возвращает экземпляр сериализатора. * `get_paginated_response(self, data)` - Возвращает объект `Response` в стиле paginated. * `paginate_queryset(self, queryset)` - Пагинация набора запросов, если требуется, возвращает либо объект страницы, либо `None`, если пагинация не настроена для этого представления. * `filter_queryset(self, queryset)` - Получив набор запросов, отфильтровать его с помощью используемых бэкендов фильтрации, возвращая новый набор запросов. --- # Миксины Mixin-классы предоставляют действия, которые используются для обеспечения базового поведения представления. Обратите внимание, что mixin-классы предоставляют методы действий, а не определяют методы обработчиков, такие как `.get()` и `.post()`, напрямую. Это позволяет более гибко компоновать поведение. Mixin-классы могут быть импортированы из `rest_framework.mixins`. ## ListModelMixin Предоставляет метод `.list(request, *args, **kwargs)`, который реализует перечисление набора запросов. Если набор запросов заполнен, возвращается ответ `200 OK` с сериализованным представлением набора запросов в качестве тела ответа. По желанию данные ответа могут быть постраничными. ## CreateModelMixin Предоставляет метод `.create(request, *args, **kwargs)`, который реализует создание и сохранение нового экземпляра модели. Если объект создан, возвращается ответ `201 Created` с сериализованным представлением объекта в качестве тела ответа. Если представление содержит ключ с именем `url`, то заголовок `Location` ответа будет заполнен этим значением. Если данные запроса, предоставленные для создания объекта, были недействительными, будет возвращен ответ `400 Bad Request`, а в теле ответа будет содержаться информация об ошибке. ## RetrieveModelMixin Предоставляет метод `.retrieve(request, *args, **kwargs)`, который реализует возврат существующего экземпляра модели в ответ. Если объект может быть получен, то возвращается ответ `200 OK` с сериализованным представлением объекта в качестве тела ответа. В противном случае будет возвращен ответ `404 Not Found`. ## UpdateModelMixin Предоставляет метод `.update(request, *args, **kwargs)`, который реализует обновление и сохранение существующего экземпляра модели. Также предоставляет метод `.partial_update(request, *args, **kwargs)`, который похож на метод `update`, за исключением того, что все поля для обновления будут необязательными. Это позволяет поддерживать HTTP-запросы `PATCH`. Если объект обновлен, возвращается ответ `200 OK` с сериализованным представлением объекта в качестве тела ответа. Если данные запроса, предоставленные для обновления объекта, были недействительными, будет возвращен ответ `400 Bad Request`, в теле которого будет содержаться информация об ошибке. ## DestroyModelMixin Предоставляет метод `.destroy(request, *args, **kwargs)`, который реализует удаление существующего экземпляра модели. Если объект удален, возвращается ответ `204 No Content`, в противном случае возвращается ответ `404 Not Found`. --- # Классы специфичных представлений Следующие классы являются специфичными общими представлениями. Если вы используете общие представления, то обычно вы работаете именно на этом уровне, если только вам не нужно сильно измененное поведение. Классы представления могут быть импортированы из `rest_framework.generics`. ## CreateAPIView Используется только для **создания** конечных точек. Предоставляет обработчик метода `post`. Расширяет: [GenericAPIView](#genericapiview), [CreateModelMixin](#createmodelmixin) ## ListAPIView Используется для конечных точек **только для чтения** для представления **коллекции экземпляров модели**. Предоставляет обработчик метода `get`. Расширяет: [GenericAPIView](#genericapiview), [ListModelMixin](#listmodelmixin) ## RetrieveAPIView Используется для конечных точек **только для чтения** для представления **одного экземпляра модели**. Предоставляет обработчик метода `get`. Расширяет: [GenericAPIView](#genericapiview), [RetrieveModelMixin](#retrievemodelmixin) ## DestroyAPIView Используется для **только для удаления** конечных точек для **одного экземпляра модели**. Предоставляет обработчик метода `delete`. Расширяет: [GenericAPIView](#genericapiview), [DestroyModelMixin](#destroymodelmixin) ## UpdateAPIView Используется для **только для обновления** конечных точек для **одного экземпляра модели**. Предоставляет обработчики методов `put` и `patch`. Расширяет: [GenericAPIView](#genericapiview), [UpdateModelMixin](#updatemodelmixin) ## ListCreateAPIView Используется для конечных точек **чтения-записи** для представления **коллекции экземпляров модели**. Предоставляет обработчики методов `get` и `post`. Расширяет: [GenericAPIView](#genericapiview), [ListModelMixin](#listmodelmixin), [CreateModelMixin](#createmodelmixin) ## RetrieveUpdateAPIView Используется для **чтения или обновления** конечных точек для представления **одного экземпляра модели**. Предоставляет обработчики методов `get`, `put` и `patch`. Расширяет: [GenericAPIView](#genericapiview), [RetrieveModelMixin](#retrievemodelmixin), [UpdateModelMixin](#updatemodelmixin) ## RetrieveDestroyAPIView Используется для конечных точек **чтения или удаления** для представления **одного экземпляра модели**. Предоставляет обработчики методов `get` и `delete`. Расширяет: [GenericAPIView](#genericapiview), [RetrieveModelMixin](#retrievemodelmixin), [DestroyModelMixin](#destroymodelmixin) ## RetrieveUpdateDestroyAPIView Используется для конечных точек **чтение-запись-удаление** для представления **одного экземпляра модели**. Предоставляет обработчики методов `get`, `put`, `patch` и `delete`. Расширяет: [GenericAPIView](#genericapiview), [RetrieveModelMixin](#retrievemodelmixin), [UpdateModelMixin](#updatemodelmixin), [DestroyModelMixin](#destroymodelmixin) --- # Настройка общих представлений Часто вы хотите использовать существующие типовые представления, но использовать несколько измененное поведение. Если вы столкнулись с повторным использованием некоторого настроенного поведения в нескольких местах, вы можете захотеть отрефакторить это поведение в общий класс, который затем можно просто применить к любому представлению или набору представлений по мере необходимости. ## Создание пользовательских миксинов Например, если вам нужно искать объекты на основе нескольких полей в URL conf, вы можете создать mixin-класс, подобный следующему: ```python class MultipleFieldLookupMixin: """ Apply this mixin to any view or viewset to get multiple field filtering based on a `lookup_fields` attribute, instead of the default single field filtering. """ def get_object(self): queryset = self.get_queryset() # Get the base queryset queryset = self.filter_queryset(queryset) # Apply any filter backends filter = {} for field in self.lookup_fields: if self.kwargs.get(field): # Ignore empty fields. filter[field] = self.kwargs[field] obj = get_object_or_404(queryset, **filter) # Lookup the object self.check_object_permissions(self.request, obj) return obj ``` Затем вы можете просто применить этот миксин к представлению или набору представлений в любое время, когда вам нужно применить пользовательское поведение. ```python class RetrieveUserView(MultipleFieldLookupMixin, generics.RetrieveAPIView): queryset = User.objects.all() serializer_class = UserSerializer lookup_fields = ['account', 'username'] ``` Использование пользовательских миксинов - хороший вариант, если у вас есть пользовательское поведение, которое необходимо использовать. ## Создание пользовательских базовых классов Если вы используете миксин в нескольких представлениях, вы можете пойти дальше и создать свой собственный набор базовых представлений, которые затем можно использовать во всем проекте. Например: ```python class BaseRetrieveView(MultipleFieldLookupMixin, generics.RetrieveAPIView): pass class BaseRetrieveUpdateDestroyView(MultipleFieldLookupMixin, generics.RetrieveUpdateDestroyAPIView): pass ``` Использование пользовательских базовых классов является хорошим вариантом, если у вас есть пользовательское поведение, которое последовательно должно повторяться в большом количестве представлений в вашем проекте. --- # PUT как создание До версии 3.0 миксины DRF рассматривали `PUT` как операцию обновления или создания, в зависимости от того, существовал ли уже объект или нет. Разрешение `PUT` в качестве операций создания является проблематичным, поскольку оно обязательно раскрывает информацию о существовании или несуществовании объектов. Также не очевидно, что прозрачное разрешение повторного создания ранее удаленных экземпляров обязательно является лучшим поведением по умолчанию, чем простое возвращение ответов `404`. Оба стиля "`PUT` как 404" и "`PUT` как создание" могут быть действительны в различных обстоятельствах, но начиная с версии 3.0 мы теперь используем поведение 404 по умолчанию, поскольку оно проще и очевиднее. --- # Пакеты сторонних производителей Следующие пакеты сторонних производителей предоставляют дополнительные реализации общих представлений. ## Django Rest Multiple Models [Django Rest Multiple Models](https://github.com/MattBroach/DjangoRestMultipleModels) предоставляет общее представление (и миксин) для отправки нескольких сериализованных моделей и/или наборов запросов через один запрос API. ================================================ FILE: api-guide/metadata.md ================================================ # Метаданные > [Метод `OPTIONS`] позволяет клиенту определить опции и/или требования, связанные с ресурсом, или возможности сервера, не подразумевая действия с ресурсом и не инициируя поиск ресурса. > > - [RFC7231, раздел 4.3.7.](https://tools.ietf.org/html/rfc7231#section-4.3.7) DRF включает настраиваемый механизм для определения того, как ваш API должен отвечать на запросы `OPTIONS`. Это позволяет вам возвращать схему API или другую информацию о ресурсе. В настоящее время не существует широко принятых соглашений о том, какой именно стиль ответа должен быть возвращен для HTTP `OPTIONS` запросов, поэтому мы предоставляем специальный стиль, который возвращает некоторую полезную информацию. Вот пример ответа, который демонстрирует информацию, возвращаемую по умолчанию. ```http HTTP 200 OK Allow: GET, POST, HEAD, OPTIONS Content-Type: application/json { "name": "To Do List", "description": "List existing 'To Do' items, or create a new item.", "renders": [ "application/json", "text/html" ], "parses": [ "application/json", "application/x-www-form-urlencoded", "multipart/form-data" ], "actions": { "POST": { "note": { "type": "string", "required": false, "read_only": false, "label": "title", "max_length": 100 } } } } ``` ## Установка схемы метаданных Вы можете установить класс метаданных глобально, используя ключ настройки `'DEFAULT_METADATA_CLASS'`: ```python REST_FRAMEWORK = { 'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata' } ``` Или вы можете установить класс метаданных индивидуально для представления: ```python class APIRoot(APIView): metadata_class = APIRootMetadata def get(self, request, format=None): return Response({ ... }) ``` Пакет DRF включает только одну реализацию класса метаданных, названную `SimpleMetadata`. Если вы хотите использовать альтернативный стиль, вам нужно будет реализовать собственный класс метаданных. ## Создание конечных точек схемы Если у вас есть особые требования к созданию конечных точек схемы, доступ к которым осуществляется с помощью обычных запросов `GET`, вы можете рассмотреть возможность повторного использования API метаданных для этого. Например, следующий дополнительный маршрут может быть использован в наборе представлений для обеспечения конечной точки схемы со ссылкой. ```python @action(methods=['GET'], detail=False) def api_schema(self, request): meta = self.metadata_class() data = meta.determine_metadata(request, self) return Response(data) ``` Есть несколько причин, по которым вы можете выбрать такой подход, включая то, что ответы `OPTIONS` [не подлежат кэшированию](https://www.mnot.net/blog/2012/10/29/NO_OPTIONS). --- # Пользовательские классы метаданных Если вы хотите предоставить собственный класс метаданных, вам следует переопределить `BaseMetadata` и реализовать метод `determine_metadata(self, request, view)`. Полезные вещи, которые вы, возможно, захотите сделать, могут включать возврат информации о схеме, используя такой формат, как [JSON schema](https://json-schema.org/), или возврат отладочной информации для пользователей-администраторов. ## Пример Следующий класс может быть использован для ограничения информации, возвращаемой на запросы `OPTIONS`. ```python class MinimalMetadata(BaseMetadata): """ Don't include field and other information for `OPTIONS` requests. Just return the name and description. """ def determine_metadata(self, request, view): return { 'name': view.get_view_name(), 'description': view.get_view_description() } ``` Затем настройте свои параметры для использования этого пользовательского класса: ```python REST_FRAMEWORK = { 'DEFAULT_METADATA_CLASS': 'myproject.apps.core.MinimalMetadata' } ``` # Пакеты сторонних производителей Следующие пакеты сторонних производителей предоставляют дополнительные реализации метаданных. ## DRF-schema-adapter [drf-schema-adapter](https://github.com/drf-forms/drf-schema-adapter) - это набор инструментов, облегчающих предоставление информации о схемах фронтенд-фреймворкам и библиотекам. Он предоставляет миксин метаданных, а также 2 класса метаданных и несколько адаптеров, подходящих для генерации [json-schema](https://json-schema.org/), а также информации о схемах, читаемой различными библиотеками. Вы также можете написать свой собственный адаптер для работы с вашим конкретным фронтендом. Если вы захотите это сделать, он также предоставляет экспортер, который может экспортировать информацию о схеме в json-файлы. ================================================ FILE: api-guide/pagination.md ================================================ --- источник: - pagination.py --- # Пагинация > Django предоставляет несколько классов, которые помогают вам управлять постраничными данными - то есть данными, разделенными на несколько страниц, со ссылками "Предыдущая/Следующая". > > — [Django documentation](https://docs.djangoproject.com/en/stable/topics/pagination/) DRF включает поддержку настраиваемых стилей пагинации. Это позволяет изменять, как большие наборы результатов разбиваются на отдельные страницы данных. API пагинации может поддерживать любую из этих функций: * Ссылки пагинации, которые предоставляются как часть содержимого ответа. * Ссылки пагинации, включенные в заголовки ответа, такие как `Content-Range` или `Link`. В настоящее время все встроенные стили используют ссылки, включенные как часть содержимого ответа. Этот стиль более доступен при использовании API с возможностью просмотра. Пагинация выполняется автоматически, только если вы используете общие представления или наборы представлений. Если вы используете обычное `APIView`, вам нужно будет самостоятельно обратиться к API пагинации, чтобы убедиться, что вы возвращаете ответ с пагинацией. Пример смотрите в исходном коде классов `mixins.ListModelMixin` и `generics.GenericAPIView`. Пагинацию можно отключить, установив для класса пагинации значение `None`. ## Установка стиля пагинации Стиль пагинации можно задать глобально, используя ключи настройки `DEFAULT_PAGINATION_CLASS` и `PAGE_SIZE`. Например, чтобы использовать встроенную пагинацию с ограничением/смещением, вы должны сделать следующее: ```python REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 'PAGE_SIZE': 100 } ``` Обратите внимание, что необходимо задать как класс пагинации, так и размер страницы, которая будет использоваться. По умолчанию и `DEFAULT_PAGINATION_CLASS`, и `PAGE_SIZE` имеют значение `None`. Вы также можете установить класс пагинации для отдельного представления с помощью атрибута `pagination_class`. Обычно вы хотите использовать один и тот же стиль пагинации во всем API, хотя вы можете захотеть варьировать отдельные аспекты пагинации, такие как размер страницы по умолчанию или максимальный размер страницы, на основе каждого представления. ## Изменение стиля пагинации Если вы хотите изменить определенные аспекты стиля пагинации, вам нужно переопределить один из классов пагинации и установить атрибуты, которые вы хотите изменить. ```python class LargeResultsSetPagination(PageNumberPagination): page_size = 1000 page_size_query_param = 'page_size' max_page_size = 10000 class StandardResultsSetPagination(PageNumberPagination): page_size = 100 page_size_query_param = 'page_size' max_page_size = 1000 ``` Затем вы можете применить ваш новый стиль к представлению с помощью атрибута `pagination_class`: ```python class BillingRecordsView(generics.ListAPIView): queryset = Billing.objects.all() serializer_class = BillingRecordsSerializer pagination_class = LargeResultsSetPagination ``` Или примените стиль глобально, используя ключ настройки `DEFAULT_PAGINATION_CLASS`. Например: ```python REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'apps.core.pagination.StandardResultsSetPagination' } ``` --- # Описание API ## PageNumberPagination Этот стиль пагинации принимает номер страницы с одним номером в параметрах запроса. **Запрос**: ```http GET https://api.example.org/accounts/?page=4 ``` **Ответ**: ```http HTTP 200 OK { "count": 1023, "next": "https://api.example.org/accounts/?page=5", "previous": "https://api.example.org/accounts/?page=3", "results": [ … ] } ``` #### Настройка Чтобы включить стиль `PageNumberPagination` глобально, используйте следующую конфигурацию и установите `PAGE_SIZE` по желанию: ```python REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 100 } ``` В подклассах `GenericAPIView` вы также можете установить атрибут `pagination_class` для выбора `PageNumberPagination` на основе каждого вида. #### Конфигурация Класс `PageNumberPagination` включает ряд атрибутов, которые могут быть переопределены для изменения стиля пагинации. Чтобы установить эти атрибуты, необходимо переопределить класс `PageNumberPagination`, а затем включить свой собственный класс пагинации, как указано выше. * `django_paginator_class` - Класс Django Paginator, который будет использоваться. По умолчанию это `django.core.paginator.Paginator`, что должно быть хорошо для большинства случаев использования. * `page_size` - Числовое значение, указывающее размер страницы. Если установлено, оно отменяет настройку `PAGE_SIZE`. По умолчанию имеет то же значение, что и ключ настройки `PAGE_SIZE`. * `page_query_param` - Строковое значение, указывающее имя параметра запроса, который будет использоваться для управления пагинацией. * `page_size_query_param` - Если установлено, это строковое значение, указывающее имя параметра запроса, который позволяет клиенту устанавливать размер страницы на основе каждого запроса. По умолчанию `None`, что означает, что клиент не может контролировать размер запрашиваемой страницы. * `max_page_size` - Если установлено, это числовое значение, указывающее на максимально допустимый размер запрашиваемой страницы. Этот атрибут действителен, только если `page_size_query_param` также установлен. * `last_page_strings` - Список или кортеж строковых значений, указывающих на значения, которые могут быть использованы с `page_query_param` для запроса последней страницы в наборе. По умолчанию `('last',)`. Например, используйте `?page=last` для запроса сразу последней страницы. * `template` - Имя шаблона для использования при отображении элементов управления пагинацией в Web-интерфейсе API. Может быть переопределено для изменения стиля рендеринга или установлено в `None` для полного отключения HTML элементов управления пагинацией. По умолчанию используется `"rest_framework/pagination/numbers.html"`. --- ## LimitOffsetPagination Этот стиль пагинации повторяет синтаксис, используемый при поиске нескольких записей в базе данных. Клиент включает в себя как `'limit'`, так и параметр запроса `'offset'`. Лимит указывает на максимальное количество возвращаемых элементов и эквивалентен `page_size` в других стилях. Смещение указывает начальную позицию запроса по отношению к полному набору непагинированных элементов. **Запрос**: ```http GET https://api.example.org/accounts/?limit=100&offset=400 ``` **Ответ**: ```http HTTP 200 OK { "count": 1023, "next": "https://api.example.org/accounts/?limit=100&offset=500", "previous": "https://api.example.org/accounts/?limit=100&offset=300", "results": [ … ] } ``` #### Подключение Чтобы включить стиль `LimitOffsetPagination` глобально, используйте следующую конфигурацию: ```python REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination' } ``` По желанию вы также можете задать ключ `PAGE_SIZE`. Если параметр `PAGE_SIZE` также используется, то параметр запроса `limit` будет необязательным и может быть опущен клиентом. В подклассах `GenericAPIView` вы также можете установить атрибут `pagination_class` для выбора `LimitOffsetPagination` для каждого представления. #### Настройка Класс `LimitOffsetPagination` включает ряд атрибутов, которые могут быть переопределены для изменения стиля пагинации. Чтобы установить эти атрибуты, вы должны переопределить класс `LimitOffsetPagination`, а затем включить свой собственный класс пагинации, как указано выше. * `default_limit` - Числовое значение, указывающее предел, который следует использовать, если он не указан клиентом в параметре запроса. По умолчанию имеет то же значение, что и ключ настройки `PAGE_SIZE`. * `limit_query_param` - Строковое значение, указывающее имя параметра запроса "limit". По умолчанию имеет значение `'limit'`. * `offset_query_param` - Строковое значение, указывающее имя параметра запроса "offset". По умолчанию имеет значение `'offset'`. * `max_limit` - Если установлено, то это числовое значение, указывающее на максимально допустимый лимит, который может быть запрошен клиентом. По умолчанию `None`. * `template` - Имя шаблона, который будет использоваться при отображении элементов управления пагинацией в Web-интерфейсе API. Может быть переопределено для изменения стиля рендеринга или установлено в `None` для полного отключения HTML элементов управления пагинацией. По умолчанию используется `"rest_framework/pagination/numbers.html"`. --- ## CursorPagination Пагинация на основе курсора представляет непрозрачный индикатор `cursor`, который клиент может использовать для просмотра набора результатов. Этот стиль пагинации представляет только элементы управления перемоткой вперед и назад и не позволяет клиенту переходить к произвольным позициям. Пагинация на основе курсора требует наличия уникального, неизменного порядка следования элементов в наборе результатов. Обычно таким упорядочиванием может быть временная метка создания записей, так как она представляет собой последовательный порядок для постраничного просмотра. Пагинация на основе курсора является более сложной, чем другие схемы. Она также требует, чтобы набор результатов представлял фиксированный порядок, и не позволяет клиенту произвольно индексировать набор результатов. Однако она обеспечивает следующие преимущества: * Обеспечивает последовательное представление пагинации. При правильном использовании `CursorPagination` гарантирует, что клиент никогда не увидит один и тот же элемент дважды при листании записей, даже если новые элементы вставляются другими клиентами во время процесса пагинации. * Поддержка использования с очень большими наборами данных. При работе с очень большими наборами данных пагинация с использованием стилей пагинации на основе смещения может стать неэффективной или непригодной для использования. Вместо этого схемы пагинации на основе курсора имеют свойства фиксированного времени и не замедляются при увеличении размера набора данных. #### Подробности и ограничения Правильное использование пагинации на основе курсора требует некоторого внимания к деталям. Вам нужно подумать о том, в каком порядке вы хотите применять схему. По умолчанию используется порядок по `"-created"`. Это предполагает, что **в экземплярах модели должно быть поле временной метки "created"**, и будет представлено постраничное представление в стиле "временной шкалы", где первыми будут самые последние добавленные элементы. Вы можете изменить порядок, переопределив атрибут `'ordering'` класса пагинации, или используя класс фильтра `OrderingFilter` вместе с `CursorPagination`. При использовании `OrderingFilter` следует тщательно продумать ограничение полей, по которым пользователь может делать заказ. Правильное использование пагинации курсора должно иметь поле упорядочивания, которое удовлетворяет следующим требованиям: * Должно быть неизменным значением, таким как временная метка, slug или другое поле, которое устанавливается только один раз, при создании. * Должно быть уникальным или почти уникальным. Хорошим примером являются временные метки с точностью до миллисекунды. Эта реализация пагинации курсора использует интеллектуальный стиль "позиция плюс смещение", что позволяет ей правильно поддерживать не строго уникальные значения в качестве упорядочивания. * Должно быть не нулевым значением, которое можно принудительно преобразовать в строку. * Не должно быть плавающей точкой. Ошибки точности легко приводят к неправильным результатам. **Совет**: используйте вместо этого десятичные числа. (Если у вас уже есть поле с плавающей запятой и вам нужно сделать постраничную запись по нему, можно воспользоваться командой [пример подкласса `CursorPagination`, который использует десятичные числа для ограничения точности, доступен здесь] (https://gist.github.com/keturn/8bc88525a183fd41c73ffb729b8865be#file-fpcursorpagination-py).) * Поле должно иметь индекс базы данных. Использование поля упорядочивания, которое не удовлетворяет этим ограничениям, как правило, будет работать, но вы потеряете некоторые преимущества пагинации курсора. Для получения более подробной технической информации о реализации, которую мы используем для пагинации курсоров, в статье ["Building cursors for the Disqus API"](https://cra.mr/2011/03/08/building-cursors-for-the-disqus-api) блога дается хороший обзор основного подхода. #### Подключение Чтобы включить стиль `CursorPagination` глобально, используйте следующую конфигурацию, изменяя `PAGE_SIZE` по желанию: ```python REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination', 'PAGE_SIZE': 100 } ``` В подклассах `GenericAPIView` вы также можете установить атрибут `pagination_class` для выбора `CursorPagination` на основе каждого вида. #### Настройка Класс `CursorPagination` включает ряд атрибутов, которые могут быть переопределены для изменения стиля пагинации. Чтобы установить эти атрибуты, необходимо переопределить класс `CursorPagination`, а затем включить свой собственный класс пагинации, как указано выше. * `page_size` - числовое значение, указывающее размер страницы. Если установлено, оно отменяет настройку `PAGE_SIZE`. По умолчанию имеет то же значение, что и ключ настройки `PAGE_SIZE`. * `cursor_query_param` - Строковое значение, указывающее имя параметра запроса "cursor". По умолчанию `'cursor'`. * `orderering` - Это должна быть строка или список строк, указывающих на поле, к которому будет применяться пагинация на основе курсора. Например: `ordering = 'slug'`. По умолчанию используется `-created`. Это значение также может быть переопределено с помощью `OrderingFilter` в представлении. * `template` - Имя шаблона, который будет использоваться при отображении элементов управления пагинацией в API просмотра. Может быть переопределено для изменения стиля рендеринга или установлено в `None` для полного отключения HTML элементов управления пагинацией. По умолчанию используется `"rest_framework/pagination/previous_and_next.html"`. --- # Пользовательские стили пагинации Чтобы создать собственный класс сериализатора пагинации, необходимо унаследовать подкласс `pagination.BasePagination`, переопределить методы `paginate_queryset(self, queryset, request, view=None)` и `get_paginated_response(self, data)`: * Метод `paginate_queryset` передается начальному кверисету и должен возвращать итерируемый объект. Этот объект содержит только данные запрашиваемой страницы. * Метод `get_paginated_response` передается сериализованным данным страницы и должен возвращать экземпляр `Response`. Обратите внимание, что метод `paginate_queryset` может установить состояние экземпляра пагинации, которое впоследствии может быть использовано методом `get_paginated_response`. ## Пример Предположим, мы хотим заменить стандартный стиль вывода пагинации на модифицированный формат, который включает следующую и предыдущую ссылки во вложенном ключе `'links'`. Мы можем указать пользовательский класс пагинации следующим образом: ```python class CustomPagination(pagination.PageNumberPagination): def get_paginated_response(self, data): return Response({ 'links': { 'next': self.get_next_link(), 'previous': self.get_previous_link() }, 'count': self.page.paginator.count, 'results': data }) ``` Затем нам нужно будет установить пользовательский класс в нашей конфигурации: ```python REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'my_project.apps.core.pagination.CustomPagination', 'PAGE_SIZE': 100 } ``` Обратите внимание, что если вам важно, как порядок ключей отображается в ответах в Web-интерфейсе API, вы можете использовать `OrderedDict` при построении тела постраничных ответов, но это необязательно. ## Использование вашего пользовательского класса пагинации Чтобы ваш пользовательский класс пагинации использовался по умолчанию, используйте параметр `DEFAULT_PAGINATION_CLASS`: ```python REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'my_project.apps.core.pagination.LinkHeaderPagination', 'PAGE_SIZE': 100 } ``` Ответы API для конечных точек списка теперь будут включать заголовок `Link`, вместо того чтобы, например, включать ссылки пагинации как часть тела ответа: ![](https://github.com/encode/django-rest-framework/raw/main/docs/img/link-header-pagination.png) *Настраиваемый стиль пагинации с использованием заголовка `Link`.* --- # Элементы управления пагинацией HTML По умолчанию использование классов пагинации приводит к отображению элементов управления пагинацией HTML в Web-интерфейсе API. Существует два встроенных стиля отображения. Классы `PageNumberPagination` и `LimitOffsetPagination` отображают список номеров страниц с предыдущим и следующим элементами управления. Класс `CursorPagination` отображает более простой стиль, в котором отображаются только предыдущий и следующий элементы управления. ## Настройка элементов управления Вы можете переопределить шаблоны, которые отображают элементы управления пагинацией HTML. Есть два встроенных стиля: * `'rest_framework/pagination/numbers.html'` * `'rest_framework/pagination/previous_and_next.html'` Предоставление шаблона с любым из этих путей в глобальном каталоге шаблонов переопределит рендеринг по умолчанию для соответствующих классов пагинации. В качестве альтернативы вы можете полностью отключить элементы управления HTML-пагинацией, создав подкласс одного из существующих классов и установив `template = None` в качестве атрибута класса. Затем вам нужно будет настроить ключ параметров `DEFAULT_PAGINATION_CLASS`, чтобы использовать ваш пользовательский класс в качестве стиля пагинации по умолчанию. #### Низкоуровневый API Низкоуровневый API для определения того, должен ли класс пагинации отображать элементы управления или нет, раскрывается как атрибут `display_page_controls` на экземпляре пагинации. Пользовательские классы пагинации должны быть установлены в `True` в методе `paginate_queryset`, если они требуют отображения элементов управления пагинацией HTML. Методы `.to_html()` и `.get_html_context()` также могут быть переопределены в пользовательском классе пагинации для дальнейшей настройки отображения элементов управления. --- # Пакеты сторонних производителей Также доступны следующие пакеты сторонних производителей. ## DRF-extensions Пакет [`DRF-extensions`](https://chibisov.github.io/drf-extensions/docs/) включает класс-миксин [`PaginateByMaxMixin`](https://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin), который позволяет вашим клиентам API указывать `?page_size=max` для получения максимально допустимого размера страницы. ## drf-proxy-pagination Пакет [`drf-proxy-pagination`](https://github.com/tuffnatty/drf-proxy-pagination) включает класс `ProxyPagination`, который позволяет выбирать класс пагинации с помощью параметра запроса. ## link-header-pagination Пакет [`django-rest-framework-link-header-pagination`](https://github.com/tbeadle/django-rest-framework-link-header-pagination) включает класс `LinkHeaderPagination`, который обеспечивает пагинацию через HTTP-заголовок `Link`, как описано в [документации GitHub REST API](https://docs.github.com/en/rest/guides/traversing-with-pagination). ================================================ FILE: api-guide/parsers.md ================================================ --- источник: - parsers.py --- # Парсеры > Взаимодействующие с машинами веб-сервисы обычно используют более структурированные форматы для отправки данных, чем данные формы, поскольку они отправляют более сложные данные, чем простые формы > > — Malcom Tredinnick, [Django developers group](https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion) DRF включает ряд встроенных классов `Parser`, которые позволяют принимать запросы с различными типами носителей. Также есть поддержка определения собственных парсеров, что дает вам гибкость в определении типов медиа, которые принимает ваш API. ## Как определяется синтаксический анализатор Набор допустимых парсеров для представления всегда определяется как список классов. Когда происходит обращение к `request.data`, DRF изучает заголовок `Content-Type` входящего запроса и определяет, какой парсер использовать для разбора содержимого запроса. --- **Примечание**: При разработке клиентских приложений всегда помните о том, что при отправке данных в HTTP-запросе нужно обязательно устанавливать заголовок `Content-Type`. Если вы не зададите тип содержимого, большинство клиентов по умолчанию будут использовать `'application/x-www-form-urlencoded'`, что может оказаться не тем, чего вы хотите. В качестве примера, если вы отправляете закодированные данные `json` с помощью jQuery с методом [.ajax()](https://api.jquery.com/jQuery.ajax/), вы должны обязательно включить параметр `contentType: 'application/json'`. --- ## Настройка парсеров Набор парсеров по умолчанию можно задать глобально, используя параметр `DEFAULT_PARSER_CLASSES`. Например, следующие настройки разрешают только запросы с содержимым `JSON`, вместо стандартного JSON или данных формы. ```python REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': [ 'rest_framework.parsers.JSONParser', ] } ``` Вы также можете установить парсеры, используемые для отдельного представления или набора представлений, используя представления на основе класса `APIView`. ```python from rest_framework.parsers import JSONParser from rest_framework.response import Response from rest_framework.views import APIView class ExampleView(APIView): """ A view that can accept POST requests with JSON content. """ parser_classes = [JSONParser] def post(self, request, format=None): return Response({'received data': request.data}) ``` Или, если вы используете декоратор `@api_view` с представлениями, основанными на функциях. ```python from rest_framework.decorators import api_view from rest_framework.decorators import parser_classes from rest_framework.parsers import JSONParser @api_view(['POST']) @parser_classes([JSONParser]) def example_view(request, format=None): """ A view that can accept POST requests with JSON content. """ return Response({'received data': request.data}) ``` --- # API Reference ## JSONParser Разбирает `JSON` содержимое запроса. `request.data` будет заполнен словарем данных. **.media_type**: `application/json`. ## FormParser Разбирает содержимое HTML-формы. `request.data` будет заполнен `QueryDict` данных. Для полной поддержки данных HTML-формы обычно требуется использовать `FormParser` и `MultiPartParser` вместе. **.media_type**: `application/x-www-form-urlencoded`. ## MultiPartParser Разбирает содержимое многокомпонентной HTML-формы, которая поддерживает загрузку файлов. `request.data` и `request.FILES` будут заполнены `QueryDict` и `MultiValueDict` соответственно. Для полной поддержки данных HTML-формы обычно требуется использовать `FormParser` и `MultiPartParser` вместе. **.media_type**: `multipart/form-data`. ## FileUploadParser Разбирает необработанное содержимое загружаемого файла. Свойство `request.data` будет представлять собой словарь с единственным ключом `'file'`, содержащим загруженный файл. Если представление, используемое с `FileUploadParser`, вызывается с именованным аргументом URL `filename`, то этот аргумент будет использоваться в качестве имени файла. Если он вызывается без именованного аргумента URL `filename`, то клиент должен установить имя файла в HTTP-заголовке `Content-Disposition`. Например, `Content-Disposition: attachment; filename=upload.jpg`. **.media_type**: `*/*` ##### Примечания: * `FileUploadParser` предназначен для использования с собственными клиентами, которые могут загружать файл как запрос необработанных данных. Для веб-загрузки или для собственных клиентов с поддержкой многочастной загрузки вместо него следует использовать `MultiPartParser`. * Поскольку `media_type` этого парсера соответствует любому типу содержимого, `FileUploadParser` обычно должен быть единственным парсером, установленным в представлении API. * `FileUploadParser` учитывает стандартную настройку Django `FILE_UPLOAD_HANDLERS` и атрибут `request.upload_handlers`. Более подробную информацию смотрите в [документации Django](https://docs.djangoproject.com/en/stable/topics/http/file-uploads/#upload-handlers). ##### Базовый пример использования: ```python # views.py class FileUploadView(views.APIView): parser_classes = [FileUploadParser] def put(self, request, filename, format=None): file_obj = request.data['file'] # ... # do some stuff with uploaded file # ... return Response(status=204) # urls.py urlpatterns = [ # ... re_path(r'^upload/(?P[^/]+)$', FileUploadView.as_view()) ] ``` --- # Пользовательские синтаксические анализаторы Для реализации пользовательского парсера необходимо переопределить `BaseParser`, установить свойство `.media_type` и реализовать метод `.parse(self, stream, media_type, parser_context)`. Метод должен возвращать данные, которые будут использоваться для заполнения свойства `request.data`. Аргументами, передаваемыми в `.parse()`, являются: ### stream Потокоподобный объект, представляющий тело запроса. ### media_type Необзательно. Если указано, это тип носителя содержимого входящего запроса. В зависимости от заголовка `Content-Type:` запроса, он может быть более конкретным, чем атрибут `media_type` рендерера, и может включать параметры типа медиа. Например, `"text/plain; charset=utf-8"`. ### parser_context Необзательно. Если этот аргумент указан, то он будет представлять собой словарь, содержащий любой дополнительный контекст, который может потребоваться для разбора содержимого запроса. По умолчанию сюда входят следующие ключи: `view`, `request`, `args`, `kwargs`. ## Пример Ниже приведен пример анализатора обычного текста, который заполнит свойство `request.data` строкой, представляющей тело запроса. ```python class PlainTextParser(BaseParser): """ Plain text parser. """ media_type = 'text/plain' def parse(self, stream, media_type=None, parser_context=None): """ Simply return a string representing the body of the request. """ return stream.read() ``` --- # Пакеты сторонних производителей Также доступны следующие пакеты сторонних производителей. ## YAML [REST framework YAML](https://jpadilla.github.io/django-rest-framework-yaml/) обеспечивает поддержку разбора и рендеринга [YAML](http://www.yaml.org/). Ранее он был включен непосредственно в пакет DRF, а теперь поддерживается как сторонний пакет. #### Установка и настройка Установите с помощью pip. ```bash $ pip install djangorestframework-yaml ``` Измените настройки DRF. ```python REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': [ 'rest_framework_yaml.parsers.YAMLParser', ], 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework_yaml.renderers.YAMLRenderer', ], } ``` ## XML [REST Framework XML](https://jpadilla.github.io/django-rest-framework-xml/) предоставляет простой неформальный формат XML. Ранее он был включен непосредственно в пакет DRF, а теперь поддерживается как сторонний пакет. #### Установка и настройка Установите с помощью pip. ```bash $ pip install djangorestframework-xml ``` Измените настройки DRF. ```python REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': [ 'rest_framework_xml.parsers.XMLParser', ], 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework_xml.renderers.XMLRenderer', ], } ``` ## MessagePack [MessagePack](https://github.com/juanriaza/django-rest-framework-msgpack) - это быстрый и эффективный формат двоичной сериализации. [Juan Riaza](https://github.com/juanriaza) поддерживает пакет [djangorestframework-msgpack](https://github.com/juanriaza/django-rest-framework-msgpack), который обеспечивает поддержку рендеринга и парсера MessagePack для DRF. ## CamelCase JSON [djangorestframework-camel-case](https://github.com/vbabiy/djangorestframework-camel-case) предоставляет рендереры и парсеры JSON в верблюжьем регистре для DRF. Это позволяет сериализаторам использовать имена полей в стиле Python с подчеркиванием, но отображать их в API как имена полей в верблюжьем регистре в стиле Javascript. Поддерживается [Виталием Бабием](https://github.com/vbabiy). ================================================ FILE: api-guide/permissions.md ================================================ --- источник: - permissions.py --- # Разрешения > Аутентификация или идентификация сами по себе обычно недостаточны для получения доступа к информации или коду. Для этого субъект, запрашивающий доступ, должен иметь авторизацию. > > — [Apple Developer Documentation](https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html) Вместе с [authentication](authentication.md) и [throttling](throttling.md) разрешения определяют, следует ли предоставить или отказать в доступе запросу. Проверка разрешений всегда выполняется в самом начале представления, до того, как будет разрешено выполнение любого другого кода. Проверки разрешений обычно используют информацию об аутентификации в свойствах `request.user` и `request.auth`, чтобы определить, должен ли входящий запрос быть разрешен. Разрешения используются для предоставления или запрета доступа различных классов пользователей к различным частям API. Самый простой стиль разрешения - разрешить доступ любому аутентифицированному пользователю и запретить доступ любому неаутентифицированному пользователю. Это соответствует классу `IsAuthenticated` в DRF. Несколько менее строгий стиль разрешения - разрешить полный доступ для аутентифицированных пользователей, но разрешить доступ только для чтения для неаутентифицированных пользователей. Это соответствует классу `IsAuthenticatedOrReadOnly` в DRF. ## Как определяются разрешения Разрешения в DRF всегда определяются как список классов разрешений. Перед запуском основной части представления проверяется каждое разрешение в списке. Если проверка какого-либо разрешения не удалась, будет вызвано исключение `exceptions.PermissionDenied` или `exceptions.NotAuthenticated`, и основное тело представления не будет запущено. Если проверка разрешения не сработала, будет возвращен ответ "403 Forbidden" или "401 Unauthorized", в соответствии со следующими правилами: * Запрос был успешно аутентифицирован, но в разрешении было отказано. *— Будет возвращен ответ HTTP 403 Forbidden.* * Запрос не был успешно аутентифицирован, и класс аутентификации с наивысшим приоритетом *не использует* заголовки `WWW-Authenticate`. *— Будет возвращен ответ HTTP 403 Forbidden.* * Запрос не был успешно аутентифицирован, и класс аутентификации с наивысшим приоритетом *использует* заголовки `WWW-Authenticate`. *— Будет возвращен ответ HTTP 401 Unauthorized с соответствующим заголовком `WWW-Authenticate`. ## Разрешения на уровне объекта Разрешения DRF также поддерживают разрешение на уровне объекта. Разрешения на уровне объекта используются для определения того, разрешено ли пользователю действовать с определенным объектом, который обычно является экземпляром модели. Разрешения на уровне объекта запускаются общими представлениями DRF при вызове `.get_object()`. Как и в случае с разрешениями на уровне представления, исключение `exceptions.PermissionDenied` будет поднято, если пользователю не разрешено действовать с данным объектом. Если вы пишете собственные представления и хотите обеспечить разрешения на уровне объекта, или если вы переопределите метод `get_object` в общем представлении, то вам нужно будет явно вызвать метод `.check_object_permissions(request, obj)` в представлении в тот момент, когда вы извлекли объект. Это либо вызовет исключение `PermissionDenied` или `NotAuthenticated`, либо просто вернет, если представление имеет соответствующие разрешения. Например: ```python def get_object(self): obj = get_object_or_404(self.get_queryset(), pk=self.kwargs["pk"]) self.check_object_permissions(self.request, obj) return obj ``` --- **Примечание**: За исключением `DjangoObjectPermissions`, предоставленные классы разрешений в `rest_framework.permissions` **не** реализуют методы, необходимые для проверки разрешений объектов. Если вы хотите использовать предоставленные классы разрешений для проверки разрешений объектов, **вы должны** подклассифицировать их и реализовать метод `has_object_permission()`, описанный в разделе [*Пользовательские разрешения*](#пользовательские-разрешения) (ниже). --- #### Ограничения разрешений на уровне объекта По причинам производительности общие представления не будут автоматически применять разрешения на уровне объекта к каждому экземпляру в наборе запросов при возврате списка объектов. Часто при использовании разрешений на уровне объектов вы также хотите [фильтровать QuerySet](filtering.md) соответствующим образом, чтобы убедиться, что пользователи имеют возможность просматривать только те экземпляры, которые им разрешено просматривать. Поскольку метод `get_object()` не вызывается, разрешения объектного уровня из метода `has_object_permission()` **не применяются** при создании объектов. Чтобы ограничить создание объектов, вам необходимо реализовать проверку разрешений либо в классе Serializer, либо переопределить метод `perform_create()` вашего класса ViewSet. ## Установка политики разрешений Политика разрешений по умолчанию может быть установлена глобально с помощью параметра `DEFAULT_PERMISSION_CLASSES`. Например. ```python REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ] } ``` Если этот параметр не указан, то по умолчанию он разрешает неограниченный доступ: ```python 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.AllowAny', ] ``` Вы также можете установить политику аутентификации на основе каждого представления или каждого набора представлений, используя представления на основе класса `APIView`. ```python from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView class ExampleView(APIView): permission_classes = [IsAuthenticated] def get(self, request, format=None): content = { 'status': 'request was permitted' } return Response(content) ``` Или, если вы используете декоратор `@api_view` с представлениями, основанными на функциях. ```python from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @api_view(['GET']) @permission_classes([IsAuthenticated]) def example_view(request, format=None): content = { 'status': 'request was permitted' } return Response(content) ``` --- **Примечание:** когда вы устанавливаете новые классы разрешений с помощью атрибута class или декораторов, вы говорите представлению игнорировать список по умолчанию, установленный в файле `settings.py`. --- При условии наследования от `rest_framework.permissions.BasePermission`, разрешения могут быть составлены с использованием стандартных побитовых операторов Python. Например, `IsAuthenticatedOrReadOnly` может быть записано: ```python from rest_framework.permissions import BasePermission, IsAuthenticated, SAFE_METHODS from rest_framework.response import Response from rest_framework.views import APIView class ReadOnly(BasePermission): def has_permission(self, request, view): return request.method in SAFE_METHODS class ExampleView(APIView): permission_classes = [IsAuthenticated | ReadOnly] def get(self, request, format=None): content = { 'status': 'request was permitted' } return Response(content) ``` --- **Примечание:** он поддерживает & (и), | (или) и ~ (не). --- # API Reference ## AllowAny Класс разрешения `AllowAny` разрешает неограниченный доступ, **независимо от того, был ли запрос аутентифицирован или неаутентифицирован**. Это разрешение не является строго обязательным, поскольку вы можете достичь того же результата, используя пустой список или кортеж для установки разрешений, но вы можете посчитать полезным указать этот класс, поскольку он делает намерение явным. ## IsAuthenticated Класс разрешения `IsAuthenticated` будет запрещать разрешение любому пользователю, не прошедшему аутентификацию, и разрешать в противном случае. Это разрешение подходит, если вы хотите, чтобы ваш API был доступен только зарегистрированным пользователям. ## IsAdminUser Класс разрешения `IsAdminUser` запрещает разрешение любому пользователю, если только `user.is_staff` не является `True`, в этом случае разрешение будет разрешено. Это разрешение подходит, если вы хотите, чтобы ваш API был доступен только подгруппе доверенных администраторов. ## IsAuthenticatedOrReadOnly Параметр `IsAuthenticatedOrReadOnly` позволит аутентифицированным пользователям выполнять любые запросы. Запросы для неаутентифицированных пользователей будут разрешены, только если метод запроса является одним из "безопасных" методов: `GET`, `HEAD` или `OPTIONS`. Это разрешение подходит, если вы хотите, чтобы ваш API разрешал разрешения на чтение анонимным пользователям и разрешал разрешения на запись только аутентифицированным пользователям. ## DjangoModelPermissions Этот класс разрешений связан со стандартными разрешениями Django `django.contrib.auth` [model permissions](https://docs.djangoproject.com/en/stable/topics/auth/customizing/#custom-permissions). Это разрешение должно применяться только к представлениям, имеющим свойство `.queryset` или метод `get_queryset()`. Авторизация будет предоставлена только в том случае, если пользователь *аутентифицирован* и имеет *соответствующие разрешения модели*. Соответствующая модель определяется путем проверки `get_queryset().model` или `queryset.model`. * `POST`-запросы требуют от пользователя разрешения `add` на модель. * `PUT`-запросы и `PATCH`-запросы требуют от пользователя разрешения `change` модель. * `DELETE`-запросы требуют от пользователя разрешения `delete` на модель. Поведение по умолчанию также может быть переопределено для поддержки пользовательских разрешений модели. Например, вы можете включить разрешение модели `view` для запросов `GET`. Чтобы использовать пользовательские разрешения модели, переопределите `DjangoModelPermissions` и установите свойство `.perms_map`. Подробности см. в исходном коде. ## DjangoModelPermissionsOrAnonReadOnly Аналогичен `DjangoModelPermissions`, но также позволяет неаутентифицированным пользователям иметь доступ к API только для чтения. ## DjangoObjectPermissions Этот класс разрешений связан со стандартным [object permissions framework](https://docs.djangoproject.com/en/stable/topics/auth/customizing/#handling-object-permissions) Django, который позволяет устанавливать разрешения на модели на уровне объектов. Чтобы использовать этот класс разрешений, вам также необходимо добавить бэкенд разрешений, который поддерживает разрешения на уровне объектов, например [django-guardian](https://github.com/lukaszb/django-guardian). Как и `DjangoModelPermissions`, это разрешение должно применяться только к представлениям, имеющим свойство `.queryset` или метод `.get_queryset()`. Разрешение будет предоставлено только в том случае, если пользователь *аутентифицирован* и имеет *соответствующие разрешения на объект* и *соответствующие разрешения на модель*. * `POST`-запросы требуют, чтобы пользователь имел разрешение `add` на экземпляр модели. * `PUT`-запросы и `PATCH`-запросы требуют от пользователя разрешения `изменить` на экземпляре модели. * `DELETE`-запросы требуют от пользователя разрешения `delete` на экземпляр модели. Обратите внимание, что `DjangoObjectPermissions` **не** требует пакета `django-guardian`, и должен одинаково хорошо поддерживать другие бэкенды объектного уровня. Как и в случае с `DjangoModelPermissions`, вы можете использовать пользовательские разрешения модели, переопределив `DjangoObjectPermissions` и установив свойство `.perms_map`. Подробности смотрите в исходном коде. --- **Примечание**: Если вам нужны разрешения `view` на уровне объектов для запросов `GET`, `HEAD` и `OPTIONS` и вы используете django-guardian для бэкенда разрешений на уровне объектов, вам стоит рассмотреть возможность использования класса `DjangoObjectPermissionsFilter`, предоставляемого [пакетом `djangorestframework-guardian`](https://github.com/rpkilby/django-rest-framework-guardian). Он гарантирует, что конечные точки списка возвращают только те результаты, включающие объекты, для которых у пользователя есть соответствующие разрешения на просмотр. --- # Пользовательские разрешения Чтобы реализовать пользовательское разрешение, переопределите `BasePermission` и реализуйте один или оба из следующих методов: * `.has_permission(self, request, view)` * `.has_object_permission(self, request, view, obj)` Методы должны возвращать `True`, если запрос должен получить доступ, и `False` в противном случае. Если вам нужно проверить, является ли запрос операцией чтения или записи, вы должны проверить метод запроса по константе `SAFE_METHODS`, которая представляет собой кортеж, содержащий `'GET'`, `'OPTIONS'` и `'HEAD'`. Например: ```python if request.method in permissions.SAFE_METHODS: # Check permissions for read-only request else: # Check permissions for write request ``` --- **Примечание**: Метод `has_object_permission` на уровне экземпляра будет вызван только в том случае, если проверки `has_permission` на уровне представления уже прошли. Также обратите внимание, что для того, чтобы проверки на уровне экземпляра были выполнены, код представления должен явно вызвать `.check_object_permissions(request, obj)`. Если вы используете общие представления, то это будет сделано за вас по умолчанию. (Представления, основанные на функциях, должны будут проверять разрешения объектов явно, выдавая при неудаче сообщение `PermissionDenied`). --- Пользовательские разрешения вызовут исключение `PermissionDenied`, если тест не пройдет. Чтобы изменить сообщение об ошибке, связанное с исключением, реализуйте атрибут `message` непосредственно для вашего пользовательского разрешения. В противном случае будет использоваться атрибут `default_detail` из `PermissionDenied`. Аналогично, чтобы изменить идентификатор кода, связанный с исключением, реализуйте атрибут `code` непосредственно для вашего пользовательского разрешения - иначе будет использоваться атрибут `default_code` из `PermissionDenied`. ```python from rest_framework import permissions class CustomerAccessPermission(permissions.BasePermission): message = 'Adding customers not allowed.' def has_permission(self, request, view): ... ``` ## Примеры Ниже приведен пример класса разрешения, который проверяет IP-адрес входящего запроса по списку блокировки и отклоняет запрос, если IP-адрес был заблокирован. ```python from rest_framework import permissions class BlocklistPermission(permissions.BasePermission): """ Global permission check for blocked IPs. """ def has_permission(self, request, view): ip_addr = request.META['REMOTE_ADDR'] blocked = Blocklist.objects.filter(ip_addr=ip_addr).exists() return not blocked ``` Помимо глобальных разрешений, которые выполняются для всех входящих запросов, вы также можете создавать разрешения на уровне объекта, которые выполняются только для операций, затрагивающих конкретный экземпляр объекта. Например: ```python class IsOwnerOrReadOnly(permissions.BasePermission): """ Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request, # so we'll always allow GET, HEAD or OPTIONS requests. if request.method in permissions.SAFE_METHODS: return True # Instance must have an attribute named `owner`. return obj.owner == request.user ``` Обратите внимание, что общие представления будут проверять соответствующие разрешения на уровне объекта, но если вы пишете свои собственные пользовательские представления, вам нужно убедиться, что вы сами проверяете разрешения на уровне объекта. Вы можете сделать это, вызвав `self.check_object_permissions(request, obj)` из представления, когда у вас есть экземпляр объекта. Этот вызов вызовет соответствующее исключение `APIException`, если проверка разрешений на уровне объекта завершится неудачей, а в противном случае просто вернется. Также обратите внимание, что общие представления будут проверять разрешения на уровне объекта только для представлений, которые получают один экземпляр модели. Если вам требуется фильтрация представлений списка на уровне объектов, вам нужно будет фильтровать набор запросов отдельно. Более подробную информацию смотрите в документации [filtering documentation](filtering.md). # Обзор методов ограничения доступа DRF предлагает три различных метода настройки ограничений доступа в каждом конкретном случае. Они применяются в разных сценариях и имеют различные эффекты и ограничения. * `queryset`/`get_queryset()`: Ограничивает общую видимость существующих объектов из базы данных. Кверисет ограничивает, какие объекты будут отображаться в списке и какие объекты могут быть изменены или удалены. Метод `get_queryset()` может применять различные кверисеты в зависимости от текущего действия. * `permission_classes`/`get_permissions()`: Общая проверка разрешений на основе текущего действия, запроса и целевого объекта. Разрешения на уровне объекта могут быть применены только к действиям получения, изменения и удаления. Проверки разрешений для `list` и `create` будут применены ко всему типу объекта. (В случае списка: с учетом ограничений в наборе запросов). * `serializer_class`/`get_serializer()`: Ограничения на уровне экземпляра, которые применяются ко всем объектам на входе и выходе. Сериализатор может иметь доступ к контексту запроса. Метод `get_serializer()` может применять различные сериализаторы в зависимости от текущего действия. В следующей таблице перечислены методы ограничения доступа и уровень контроля, который они обеспечивают, над какими действиями. | | `queryset` | `permission_classes` | `serializer_class` | | ------------------------------------- | ---------- | -------------------- | ------------------ | | Действие: список | глобальный | глобальный | уровень объектна\* | | Действие: создать | нет | глобальный | уровень объектна | | Действие: извлечь | глобальный | уровень объектна | уровень объектна | | Действие: обновить | глобальный | уровень объектна | уровень объектна | | Действие: partial_update | global | уровень объектна | уровень объектна | | Действие: уничтожить | глобальный | уровень объектна | нет | | Может ссылаться на действие в решении | нет\*\* | да | нет\*\* | | Может ссылаться на запрос в решении | нет\*\* | да | да | \* Класс Serializer не должен поднимать PermissionDenied в действии со списком, иначе весь список не будет возвращен. \*\* Методы `get_*()` имеют доступ к текущему представлению и могут возвращать различные экземпляры Serializer или QuerySet в зависимости от запроса или действия. --- # Пакеты сторонних производителей Также доступны следующие пакеты сторонних производителей. ## Django REST - Access Policy Пакет [Django REST - Access Policy](https://github.com/rsinger86/drf-access-policy) предоставляет способ определения сложных правил доступа в декларативных классах политик, которые прикрепляются к наборам представлений или представлениям на основе функций. Политики определяются в JSON в формате, аналогичном политикам AWS Identity & Access Management. ## Composed Permissions Пакет [Composed Permissions](https://github.com/niwibe/djangorestframework-composed-permissions) предоставляет простой способ определения сложных и многомерных (с логическими операторами) объектов разрешений, используя небольшие и многократно используемые компоненты. ## REST Condition Пакет [REST Condition](https://github.com/caxap/rest_condition) - это еще одно расширение для построения сложных разрешений простым и удобным способом. Расширение позволяет комбинировать разрешения с логическими операторами. ## DRY Rest Permissions Пакет [DRY Rest Permissions](https://github.com/FJNR-inc/dry-rest-permissions) предоставляет возможность определять различные разрешения для отдельных действий по умолчанию и пользовательских действий. Этот пакет предназначен для приложений с разрешениями, которые являются производными от отношений, определенных в модели данных приложения. Он также поддерживает проверку разрешений, возвращаемую клиентскому приложению через сериализатор API. Кроме того, он поддерживает добавление разрешений к действиям списка по умолчанию и пользовательским действиям списка для ограничения данных, которые они извлекают для каждого пользователя. ## Django Rest Framework Roles Пакет [Django Rest Framework Roles](https://github.com/computer-lab/django-rest-framework-roles) облегчает параметризацию вашего API для нескольких типов пользователей. ## Rest Framework Roles [Rest Framework Roles](https://github.com/Pithikos/rest-framework-roles) позволяет очень просто защитить представления на основе ролей. Самое главное - позволяет вам отделить логику доступности от моделей и представлений чистым человекочитаемым способом. ## Django REST Framework API Key Пакет [Django REST Framework API Key](https://florimondmanca.github.io/djangorestframework-api-key/) предоставляет классы разрешений, модели и помощники для добавления авторизации по API ключу в ваш API. Его можно использовать для авторизации внутренних или сторонних бэкендов и сервисов (т.е. *машин*), которые не имеют учетной записи пользователя. API ключи хранятся в безопасном месте с использованием инфраструктуры хэширования паролей Django, и их можно просматривать, редактировать и отзывать в любое время в админке Django. ## Django Rest Framework Role Filters Пакет [Django Rest Framework Role Filters](https://github.com/allisson/django-rest-framework-role-filters) обеспечивает простую фильтрацию по нескольким типам ролей. ## Django Rest Framework PSQ Пакет [Django Rest Framework PSQ](https://github.com/drf-psq/drf-psq) - это расширение, которое предоставляет поддержку для использования основанных на действиях **permission_classes**, **serializer_class** и **queryset**, зависящих от правил, основанных на разрешениях. ## Axioms DRF PY Пакет [Axioms DRF PY][https://github.com/abhishektiwari/axioms-drf-py] — это расширение, которое обеспечивает поддержку аутентификации и тонкой авторизации на основе заявлений (**области действия**, **роли**, **группы**, **разрешения** и т. д., включая проверки на уровне объектов) с использованием токенов JWT, выдаваемых сервером авторизации OAuth2/OIDC, включая AWS Cognito, Auth0, Okta, Microsoft Entra и т. д. ================================================ FILE: api-guide/relations.md ================================================ # Отношения сериализаторов > Структуры данных, а не алгоритмы, занимают центральное место в программировании. > > - [Роб Пайк](http://users.ece.utexas.edu/~adnan/pike.html) Реляционные поля используются для представления отношений между моделями. Они могут применяться к отношениям `ForeignKey`, `ManyToManyField` и `OneToOneField`, а также к обратным отношениям и пользовательским отношениям, таким как `GenericForeignKey`. --- **Примечание:** Реляционные поля объявляются в `relations.py`, но по соглашению вы должны импортировать их из модуля `serializers`, используя `from rest_framework import serializers` и ссылаться на поля как `serializers.`. --- **Примечание:** DRF не пытается автоматически оптимизировать передаваемые сериализаторам наборы запросов в терминах `select_related` и `prefetch_related`, поскольку это было бы слишком сложной магией. Сериализатор с полем, охватывающим отношение orm через атрибут source, может потребовать дополнительного обращения к базе данных для получения связанных объектов из базы данных. В обязанности программиста входит оптимизация запросов, чтобы избежать дополнительных обращений к базе данных, которые могут возникнуть при использовании такого сериализатора. Например, следующий сериализатор будет приводить к попаданию в базу данных каждый раз при оценке поля `tracks`, если оно не префетчено: ```python class AlbumSerializer(serializers.ModelSerializer): tracks = serializers.SlugRelatedField( many=True, read_only=True, slug_field='title' ) class Meta: model = Album fields = ['album_name', 'artist', 'tracks'] # For each album object, tracks should be fetched from database qs = Album.objects.all() print(AlbumSerializer(qs, many=True).data) ``` Если `AlbumSerializer` используется для сериализации довольно большого набора запросов с `many=True`, то это может стать серьезной проблемой производительности. Оптимизация кверисета, передаваемого `AlbumSerializer` с: ```python qs = Album.objects.prefetch_related('tracks') # No additional database hits required print(AlbumSerializer(qs, many=True).data) ``` решит эту проблему. --- #### Инспектирование отношений. При использовании класса `ModelSerializer`, поля и отношения сериализатора будут автоматически сгенерированы для вас. Проверка этих автоматически сгенерированных полей может быть полезным инструментом для определения того, как настроить стиль отношений. Для этого откройте оболочку Django, используя `python manage.py shell`, затем импортируйте класс сериализатора, инстанцируйте его и выведите представление объекта... ```python >>> from myapp.serializers import AccountSerializer >>> serializer = AccountSerializer() >>> print(repr(serializer)) AccountSerializer(): id = IntegerField(label='ID', read_only=True) name = CharField(allow_blank=True, max_length=100, required=False) owner = PrimaryKeyRelatedField(queryset=User.objects.all()) ``` # API Reference Для того чтобы объяснить различные типы реляционных полей, мы будем использовать пару простых моделей для наших примеров. Нашими моделями будут музыкальные альбомы и треки, перечисленные в каждом альбоме. ```python class Album(models.Model): album_name = models.CharField(max_length=100) artist = models.CharField(max_length=100) class Track(models.Model): album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE) order = models.IntegerField() title = models.CharField(max_length=100) duration = models.IntegerField() class Meta: unique_together = ['album', 'order'] ordering = ['order'] def __str__(self): return '%d: %s' % (self.order, self.title) ``` ## StringRelatedField `StringRelatedField` может использоваться для представления цели отношения с помощью своего метода `__str__`. Например, следующий сериализатор: ```python class AlbumSerializer(serializers.ModelSerializer): tracks = serializers.StringRelatedField(many=True) class Meta: model = Album fields = ['album_name', 'artist', 'tracks'] ``` Сериализуется в следующее представление: ```python { 'album_name': 'Things We Lost In The Fire', 'artist': 'Low', 'tracks': [ '1: Sunflower', '2: Whitetail', '3: Dinosaur Act', ... ] } ``` Это поле доступно только для чтения. **Аргументы**: * `many` - Если применяется к отношениям типа "ко многим", следует установить этот аргумент в `True`. ## PrimaryKeyRelatedField `PrimaryKeyRelatedField` может использоваться для представления цели отношения с помощью его первичного ключа. Например, следующий сериализатор: ```python class AlbumSerializer(serializers.ModelSerializer): tracks = serializers.PrimaryKeyRelatedField(many=True, read_only=True) class Meta: model = Album fields = ['album_name', 'artist', 'tracks'] ``` Сериализуется в представление, подобное этому: ```python { 'album_name': 'Undun', 'artist': 'The Roots', 'tracks': [ 89, 90, 91, ... ] } ``` По умолчанию это поле предназначено для чтения-записи, хотя вы можете изменить это поведение с помощью флага `read_only`. **Аргументы**: * `queryset` - Набор запросов, используемый для поиска экземпляра модели при проверке ввода поля. Отношения должны либо явно задать queryset, либо установить `read_only=True`. * `many` - Если применяется к отношениям типа "ко многим", вы должны установить этот аргумент в `True`. * `allow_null` - Если установить значение `True`, поле будет принимать значения `None` или пустую строку для нулевых отношений. По умолчанию `False`. * `pk_field` - Устанавливается в поле для управления сериализацией/десериализацией значения первичного ключа. Например, `pk_field=UUIDField(format='hex')` будет сериализовать первичный ключ UUID в его компактное шестнадцатеричное представление. ## HyperlinkedRelatedField `HyperlinkedRelatedField` может использоваться для представления цели отношения с помощью гиперссылки. Например, следующий сериализатор: ```python class AlbumSerializer(serializers.ModelSerializer): tracks = serializers.HyperlinkedRelatedField( many=True, read_only=True, view_name='track-detail' ) class Meta: model = Album fields = ['album_name', 'artist', 'tracks'] ``` Сериализуется в представление, подобное этому: ```python { 'album_name': 'Graceland', 'artist': 'Paul Simon', 'tracks': [ 'http://www.example.com/api/tracks/45/', 'http://www.example.com/api/tracks/46/', 'http://www.example.com/api/tracks/47/', ... ] } ``` По умолчанию это поле предназначено для чтения-записи, хотя вы можете изменить это поведение с помощью флага `read_only`. --- **Примечание**: Это поле предназначено для объектов, сопоставленных с URL, который принимает один именованный аргумент URL, заданный с помощью аргументов `lookup_field` и `lookup_url_kwarg`. Это подходит для URL, которые содержат один первичный ключ или аргумент slug как часть URL. Если вам требуется более сложное представление гиперссылок, вам необходимо настроить поле, как описано ниже в разделе [пользовательские гиперссылочные поля](#пользовательские-поля-с-гиперссылками). --- **Аргументы**: * `view_name` - Имя представления, которое должно использоваться в качестве цели отношения. Если вы используете [стандартные классы маршрутизаторов](https://www.django-rest-framework.org/api-guide/routers#defaultrouter), это будет строка с форматом `<имя модели>-detail`. **необходимо**. * `queryset` - Набор запросов, используемый для поиска экземпляра модели при проверке ввода поля. Отношения должны либо явно задать queryset, либо установить `read_only=True`. * `many` - Если применяется к отношениям типа "ко многим", вы должны установить этот аргумент в `True`. * `allow_null` - Если установить значение `True`, поле будет принимать значения `None` или пустую строку для нулевых отношений. По умолчанию `False`. * `lookup_field` - Поле цели, которое должно быть использовано для поиска. Должно соответствовать именованному аргументу URL в ссылающемся представлении. По умолчанию `'pk'`. * `lookup_url_kwarg` - Имя именованного аргумента, определенного в URL conf, который соответствует полю поиска. По умолчанию используется то же значение, что и `lookup_field`. * `format` - Если используются суффиксы формата, то поля с гиперссылками будут использовать тот же суффикс формата для цели, если это не отменено с помощью аргумента `format`. ## SlugRelatedField `SlugRelatedField` может использоваться для представления цели отношения с помощью поля цели. Например, следующий сериализатор: ```python class AlbumSerializer(serializers.ModelSerializer): tracks = serializers.SlugRelatedField( many=True, read_only=True, slug_field='title' ) class Meta: model = Album fields = ['album_name', 'artist', 'tracks'] ``` Сериализуется в представление, подобное этому: ```python { 'album_name': 'Dear John', 'artist': 'Loney Dear', 'tracks': [ 'Airport Surroundings', 'Everything Turns to You', 'I Was Only Going Out', ... ] } ``` По умолчанию это поле предназначено для чтения-записи, хотя вы можете изменить это поведение с помощью флага `read_only`. При использовании `SlugRelatedField` в качестве поля для чтения-записи вы обычно хотите убедиться, что поле slug соответствует полю модели с `unique=True`. **Аргументы**: * `slug_field` - Поле цели, которое должно быть использовано для ее представления. Это должно быть поле, которое однозначно идентифицирует любой данный экземпляр. Например, `username`. **обязательно** * `queryset` - Набор запросов, используемый для поиска экземпляра модели при проверке ввода поля. Отношения должны либо явно задать queryset, либо установить `read_only=True`. * `many` - Если применяется к отношениям типа "ко многим", вы должны установить этот аргумент в `True`. * `allow_null` - Если установить значение `True`, поле будет принимать значения `None` или пустую строку для нулевых отношений. По умолчанию установлено значение `False`. ## HyperlinkedIdentityField Это поле может применяться как отношение идентичности, например, поле `'url'` в `HyperlinkedModelSerializer`. Оно также может быть использовано для атрибута объекта. Например, следующий сериализатор: ```python class AlbumSerializer(serializers.HyperlinkedModelSerializer): track_listing = serializers.HyperlinkedIdentityField(view_name='track-list') class Meta: model = Album fields = ['album_name', 'artist', 'track_listing'] ``` Сериализуется в представление, подобное этому: ```python { 'album_name': 'The Eraser', 'artist': 'Thom Yorke', 'track_listing': 'http://www.example.com/api/track_list/12/', } ``` Это поле всегда доступно только для чтения. **Аргументы**: * `view_name` - Имя представления, которое должно использоваться в качестве цели отношения. Если вы используете [стандартные классы маршрутизаторов](https://www.django-rest-framework.org/api-guide/routers#defaultrouter), это будет строка с форматом `<имя_модели>-detail`. **необходимо**. * `lookup_field` - Поле цели, которое должно быть использовано для поиска. Должно соответствовать именованному аргументу URL в ссылающемся представлении. По умолчанию `'pk'`. * `lookup_url_kwarg` - Имя именованного слова, определенного в URL conf, который соответствует полю поиска. По умолчанию используется то же значение, что и `lookup_field`. * `format` - Если используются суффиксы формата, поля с гиперссылками будут использовать тот же суффикс формата для цели, если это не будет отменено с помощью аргумента `format`. --- # Вложенные отношения В отличие от ранее рассмотренных *ссылок* на другую сущность, ссылающаяся сущность может быть встроена или *вложена* в представление объекта, который на нее ссылается. Такие вложенные отношения могут быть выражены с помощью сериализаторов в качестве полей. Если поле используется для представления отношения "ко многим", необходимо добавить флаг `many=True` к полю сериализатора. ## Пример Например, следующий сериализатор: ```python class TrackSerializer(serializers.ModelSerializer): class Meta: model = Track fields = ['order', 'title', 'duration'] class AlbumSerializer(serializers.ModelSerializer): tracks = TrackSerializer(many=True, read_only=True) class Meta: model = Album fields = ['album_name', 'artist', 'tracks'] ``` Сериализуется во вложенное представление следующим образом: ```python >>> album = Album.objects.create(album_name="The Gray Album", artist='Danger Mouse') >>> Track.objects.create(album=album, order=1, title='Public Service Announcement', duration=245) >>> Track.objects.create(album=album, order=2, title='What More Can I Say', duration=264) >>> Track.objects.create(album=album, order=3, title='Encore', duration=159) >>> serializer = AlbumSerializer(instance=album) >>> serializer.data { 'album_name': 'The Gray Album', 'artist': 'Danger Mouse', 'tracks': [ {'order': 1, 'title': 'Public Service Announcement', 'duration': 245}, {'order': 2, 'title': 'What More Can I Say', 'duration': 264}, {'order': 3, 'title': 'Encore', 'duration': 159}, ... ], } ``` ## Записываемые вложенные сериализаторы По умолчанию вложенные сериализаторы доступны только для чтения. Если вы хотите поддерживать операции записи во вложенное поле сериализатора, вам необходимо создать методы `create()` и/или `update()`, чтобы явно указать, как должны сохраняться дочерние отношения: ```python class TrackSerializer(serializers.ModelSerializer): class Meta: model = Track fields = ['order', 'title', 'duration'] class AlbumSerializer(serializers.ModelSerializer): tracks = TrackSerializer(many=True) class Meta: model = Album fields = ['album_name', 'artist', 'tracks'] def create(self, validated_data): tracks_data = validated_data.pop('tracks') album = Album.objects.create(**validated_data) for track_data in tracks_data: Track.objects.create(album=album, **track_data) return album >>> data = { 'album_name': 'The Gray Album', 'artist': 'Danger Mouse', 'tracks': [ {'order': 1, 'title': 'Public Service Announcement', 'duration': 245}, {'order': 2, 'title': 'What More Can I Say', 'duration': 264}, {'order': 3, 'title': 'Encore', 'duration': 159}, ], } >>> serializer = AlbumSerializer(data=data) >>> serializer.is_valid() True >>> serializer.save() ``` --- # Пользовательские реляционные поля В редких случаях, когда ни один из существующих реляционных стилей не подходит для нужного вам представления, вы можете реализовать полностью пользовательское реляционное поле, которое описывает, как именно должно быть сгенерировано выходное представление из экземпляра модели. Для реализации пользовательского реляционного поля необходимо переопределить `RelatedField` и реализовать метод `.to_representation(self, value)`. Этот метод принимает цель поля в качестве аргумента `value` и должен возвращать представление, которое должно использоваться для сериализации цели. Аргумент `value` обычно представляет собой экземпляр модели. Если вы хотите реализовать реляционное поле для чтения и записи, вы должны также реализовать метод [`.to_internal_value(self, data)`](https://www.django-rest-framework.org/api-guide/serializers/#to_internal_valueself-data). Чтобы обеспечить динамический набор запросов, основанный на `context`, вы также можете переопределить `.get_queryset(self)` вместо указания `.queryset` в классе или при инициализации поля. ## Пример Например, мы можем определить реляционное поле для сериализации трека в пользовательское строковое представление, используя его порядок, название и продолжительность: ```python import time class TrackListingField(serializers.RelatedField): def to_representation(self, value): duration = time.strftime('%M:%S', time.gmtime(value.duration)) return 'Track %d: %s (%s)' % (value.order, value.name, duration) class AlbumSerializer(serializers.ModelSerializer): tracks = TrackListingField(many=True) class Meta: model = Album fields = ['album_name', 'artist', 'tracks'] ``` Это пользовательское поле затем сериализуется в следующее представление: ```python { 'album_name': 'Sometimes I Wish We Were an Eagle', 'artist': 'Bill Callahan', 'tracks': [ 'Track 1: Jim Cain (04:39)', 'Track 2: Eid Ma Clack Shaw (04:19)', 'Track 3: The Wind and the Dove (04:34)', ... ] } ``` --- # Пользовательские поля с гиперссылками В некоторых случаях вам может понадобиться настроить поведение поля с гиперссылкой, чтобы представить URL-адреса, для которых требуется более одного поля поиска. Вы можете добиться этого, переопределив `HyperlinkedRelatedField`. Есть два метода, которые могут быть переопределены: **get_url(self, obj, view_name, request, format)**. Метод `get_url` используется для сопоставления экземпляра объекта с его URL-представлением. Может вызвать ошибку `NoReverseMatch`, если атрибуты `view_name` и `lookup_field` не настроены на правильное соответствие URL conf. **get_object(self, view_name, view_args, view_kwargs)**. Если вы хотите поддерживать записываемое поле с гиперссылками, вам также потребуется переопределить `get_object`, чтобы сопоставить входящие URL обратно с объектом, который они представляют. Для полей с гиперссылками, доступных только для чтения, нет необходимости переопределять этот метод. Возвращаемое значение этого метода - объект, соответствующий аргументам URL conf. Может вызвать исключение `ObjectDoesNotExist`. ## Пример Допустим, у нас есть URL для объекта customer, который принимает два аргумента в виде ключевых слов, как показано ниже: ```python /api//customers// ``` Это не может быть представлено с помощью реализации по умолчанию, которая принимает только одно поле поиска. В этом случае нам нужно переопределить `HyperlinkedRelatedField`, чтобы получить желаемое поведение: ```python from rest_framework import serializers from rest_framework.reverse import reverse class CustomerHyperlink(serializers.HyperlinkedRelatedField): # We define these as class attributes, so we don't need to pass them as arguments. view_name = 'customer-detail' queryset = Customer.objects.all() def get_url(self, obj, view_name, request, format): url_kwargs = { 'organization_slug': obj.organization.slug, 'customer_pk': obj.pk } return reverse(view_name, kwargs=url_kwargs, request=request, format=format) def get_object(self, view_name, view_args, view_kwargs): lookup_kwargs = { 'organization__slug': view_kwargs['organization_slug'], 'pk': view_kwargs['customer_pk'] } return self.get_queryset().get(**lookup_kwargs) ``` Обратите внимание, что если вы хотите использовать этот стиль вместе с общими представлениями, то вам также необходимо переопределить `.get_object` в представлении, чтобы получить правильное поведение поиска. Обычно мы рекомендуем использовать плоский стиль для представления API, когда это возможно, но вложенный стиль URL также может быть разумным при умеренном использовании. --- # Дальнейшие примечания ## Аргумент `queryset`. Аргумент `queryset` требуется только для *записываемого* поля отношения, в этом случае он используется для выполнения поиска экземпляра модели, который отображает примитивный пользовательский ввод в экземпляр модели. В версии 2.x класс сериализатора мог *иногда* автоматически определять аргумент `queryset`, *если* использовался класс `ModelSerializer`. Теперь это поведение заменено на *всегда* использование явного аргумента `queryset` для записываемых реляционных полей. Это уменьшает количество скрытой "магии", которую обеспечивает `ModelSerializer`, делает поведение поля более понятным и гарантирует, что можно легко переходить от использования ярлыка `ModelSerializer` к использованию полностью явных классов `Serializer`. ## Настройка отображения HTML Встроенный метод `__str__` модели будет использоваться для создания строковых представлений объектов, используемых для заполнения свойства `choices`. Эти варианты используются для заполнения HTML-вводов выбора в Web-интерфейсе API. Чтобы обеспечить настраиваемое представление для таких входов, переопределите `display_value()` подкласса `RelatedField`. Этот метод получит объект модели и должен вернуть строку, подходящую для его представления. Например: ```python class TrackPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): def display_value(self, instance): return 'Track: %s' % (instance.title) ``` ## Выберите отсечение полей При отображении в Web-интерфейсе API реляционные поля по умолчанию будут отображать не более 1000 элементов для выбора. Если элементов больше, то будет отображаться отключенная опция `'More than 1000 items...'`. Это поведение предназначено для того, чтобы предотвратить невозможность отрисовки шаблона за приемлемое время из-за отображения очень большого количества связей. Есть два именованных аргумента, которые можно использовать для управления этим поведением: * `html_cutoff` - Если установлено, это будет максимальное количество вариантов выбора, которое будет отображаться в выпадающем списке HTML select. Установите значение `None`, чтобы отключить любое ограничение. По умолчанию `1000`. * `html_cutoff_text` - При установке этого параметра будет отображаться текстовый индикатор, если максимальное количество элементов было отсечено в выпадающем списке HTML select. По умолчанию `'More than {count} items...'`. Вы также можете управлять ими глобально, используя настройки `HTML_SELECT_CUTOFF` и `HTML_SELECT_CUTOFF_TEXT`. В случаях, когда отсечение вводится принудительно, вы можете использовать обычное поле ввода в HTML-форме. Вы можете сделать это, используя именованный аргумент `style`. Например: ```python assigned_to = serializers.SlugRelatedField( queryset=User.objects.all(), slug_field='username', style={'base_template': 'input.html'} ) ``` ## Обратные отношения Обратите внимание, что обратные отношения не включаются автоматически классами `ModelSerializer` и `HyperlinkedModelSerializer`. Чтобы включить обратное отношение, вы должны явно добавить его в список полей. Например: ```python class AlbumSerializer(serializers.ModelSerializer): class Meta: fields = ['tracks', ...] ``` Обычно вам нужно убедиться, что вы установили соответствующий аргумент `related_name` для отношения, который вы можете использовать в качестве имени поля. Например: ```python class Track(models.Model): album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE) ... ``` Если вы не задали связанное имя для обратного отношения, вам придется использовать автоматически сгенерированное связанное имя в аргументе `fields`. Например: ```python class AlbumSerializer(serializers.ModelSerializer): class Meta: fields = ['track_set', ...] ``` Более подробную информацию смотрите в документации Django по [обратным отношениям](https://docs.djangoproject.com/en/stable/topics/db/queries/#following-relationships-backward). ## Общие отношения Если вы хотите сериализовать общий внешний ключ, вам необходимо определить пользовательское поле, чтобы явно определить, как вы хотите сериализовать цели отношения. Например, дана следующая модель для тега, которая имеет общие отношения с другими произвольными моделями: ```python class TaggedItem(models.Model): """ Tags arbitrary model instances using a generic relation. See: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/ """ tag_name = models.SlugField() content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() tagged_object = GenericForeignKey('content_type', 'object_id') def __str__(self): return self.tag_name ``` И следующие две модели, которые могут иметь связанные теги: ```python class Bookmark(models.Model): """ A bookmark consists of a URL, and 0 or more descriptive tags. """ url = models.URLField() tags = GenericRelation(TaggedItem) class Note(models.Model): """ A note consists of some text, and 0 or more descriptive tags. """ text = models.CharField(max_length=1000) tags = GenericRelation(TaggedItem) ``` Мы можем определить пользовательское поле, которое будет использоваться для сериализации помеченных экземпляров, используя тип каждого экземпляра для определения того, как он должен быть сериализован: ```python class TaggedObjectRelatedField(serializers.RelatedField): """ A custom field to use for the `tagged_object` generic relationship. """ def to_representation(self, value): """ Serialize tagged objects to a simple textual representation. """ if isinstance(value, Bookmark): return 'Bookmark: ' + value.url elif isinstance(value, Note): return 'Note: ' + value.text raise Exception('Unexpected type of tagged object') ``` Если вам нужно, чтобы цель отношения имела вложенное представление, вы можете использовать необходимые сериализаторы внутри метода `.to_representation()`: ```python def to_representation(self, value): """ Serialize bookmark instances using a bookmark serializer, and note instances using a note serializer. """ if isinstance(value, Bookmark): serializer = BookmarkSerializer(value) elif isinstance(value, Note): serializer = NoteSerializer(value) else: raise Exception('Unexpected type of tagged object') return serializer.data ``` Обратите внимание, что обратные родовые ключи, выраженные с помощью поля `GenericRelation`, могут быть сериализованы с использованием обычных типов реляционных полей, поскольку тип цели в отношениях всегда известен. Для получения дополнительной информации смотрите [документацию Django по общим отношениям](https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#id1). ## ManyToManyFields со сквозной моделью По умолчанию реляционные поля, нацеленные на `ManyToManyField` с указанной моделью `through`, устанавливаются только для чтения. Если вы явно указываете реляционное поле, указывающее на `ManyToManyField` со сквозной моделью, обязательно установите `read_only` в `True`. Если вы хотите представить [дополнительные поля в сквозной модели](https://docs.djangoproject.com/en/stable/topics/db/models/#intermediary-manytomany), то вы можете сериализовать сквозную модель как [вложенный объект](https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects). --- # Сторонние пакеты Также доступны следующие пакеты сторонних производителей. ## DRF nested routers Пакет [drf-nested-routers](https://github.com/alanjds/drf-nested-routers) предоставляет маршрутизаторы и поля отношений для работы с вложенными ресурсами. ## Rest Framework Generic Relations Библиотека [rest-framework-generic-relations](https://github.com/Ian-Foote/rest-framework-generic-relations) обеспечивает сериализацию чтения/записи для общих внешних ключей. Библиотека [rest-framework-gm2m-relations][https://github.com/mojtabaakbari221b/rest-framework-gm2m-relations] обеспечивает сериализацию чтения/записи для [django-gm2m][https://github.com/tkhyn/django-gm2m]. ================================================ FILE: api-guide/renderers.md ================================================ # Рендереры > Прежде чем экземпляр `TemplateResponse` будет возвращен клиенту, он должен быть отрендерен. Процесс рендеринга принимает промежуточное представление шаблона и контекста и превращает его в конечный поток байтов, который может быть передан клиенту. > > - [Django documentation](https://docs.djangoproject.com/en/stable/ref/template-response/#the-rendering-process) DRF включает ряд встроенных классов Renderer, которые позволяют возвращать ответы с различными типами медиа. Также имеется поддержка определения собственных пользовательских рендереров, что дает вам гибкость в разработке собственных типов медиа. ## Как определяется рендерер Набор допустимых рендерингов для представления всегда определяется как список классов. При входе в представление DRF будет выполнять согласование содержимого входящего запроса и определять наиболее подходящий рендерер для удовлетворения запроса. Основной процесс согласования содержимого включает в себя изучение заголовка `Accept` запроса, чтобы определить, какие типы медиа ожидаются в ответе. По желанию, суффиксы формата в URL могут быть использованы для явного запроса определенного представления. Например, URL `http://example.com/api/users_count.json` может быть конечной точкой, которая всегда возвращает данные в формате JSON. Для получения дополнительной информации смотрите документацию по [согласованию контента](content-negotiation.md). ## Установка рендереров Набор рендеров по умолчанию можно задать глобально, используя параметр `DEFAULT_RENDERER_CLASSES`. Например, следующие настройки будут использовать `JSON` в качестве основного типа медиа, а также включать API самоописания. ```python REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', ] } ``` Вы также можете установить рендереры, используемые для отдельного представления или набора представлений, используя представления на основе класса `APIView`. ```python from django.contrib.auth.models import User from rest_framework.renderers import JSONRenderer from rest_framework.response import Response from rest_framework.views import APIView class UserCountView(APIView): """ A view that returns the count of active users in JSON. """ renderer_classes = [JSONRenderer] def get(self, request, format=None): user_count = User.objects.filter(active=True).count() content = {'user_count': user_count} return Response(content) ``` Или, если вы используете декоратор `@api_view` с представлениями, основанными на функциях. ```python @api_view(['GET']) @renderer_classes([JSONRenderer]) def user_count_view(request, format=None): """ A view that returns the count of active users in JSON. """ user_count = User.objects.filter(active=True).count() content = {'user_count': user_count} return Response(content) ``` ## Упорядочивание классов рендеринга Важно при определении классов рендеринга для вашего API подумать о том, какой приоритет вы хотите присвоить каждому типу медиа. Если клиент недоопределяет представления, которые он может принимать, например, посылает заголовок `Accept: */*` заголовок, или вообще не включает заголовок `Accept`, то DRF выберет первый рендерер в списке для использования в ответе. Например, если ваш API обслуживает JSON-ответы и HTML-просмотр, вы можете сделать `JSONRenderer` рендерером по умолчанию, чтобы отправлять `JSON` ответы клиентам, которые не указывают заголовок `Accept`. Если ваш API включает представления, которые могут обслуживать как обычные веб-страницы, так и ответы API в зависимости от запроса, то вы можете сделать `TemplateHTMLRenderer` рендерером по умолчанию, чтобы хорошо работать со старыми браузерами, которые отправляют [broken accept headers](http://www.gethifi.com/blog/browser-rest-http-accept-headers). --- # API Reference ## JSONRenderer Переводит данные запроса в `JSON`, используя кодировку `utf-8`. Обратите внимание, что стиль по умолчанию включает символы юникода и отображает ответ, используя компактный стиль без лишних пробелов: ```python {"unicode black star":"★","value":999} ``` Клиент может дополнительно включить параметр медиатипа `'indent'`, в этом случае возвращаемый `JSON` будет иметь отступ. Например, `Accept: application/json; indent=4`. ```python { "unicode black star": "★", "value": 999 } ``` Стиль кодировки JSON по умолчанию может быть изменен с помощью ключей настроек `UNICODE_JSON` и `COMPACT_JSON`. **.media_type**: `application/json`. **.format**: `'json'`. **.charset**: `None`. ## TemplateHTMLRenderer Рендерит данные в HTML, используя стандартный шаблонный рендеринг Django. В отличие от других рендерингов, данные, передаваемые в `Response`, не нужно сериализовать. Также, в отличие от других рендереров, вы можете включить аргумент `template_name` при создании `Response`. TemplateHTMLRenderer создаст `RequestContext`, используя `response.data` в качестве диктанта контекста, и определит имя шаблона, который будет использоваться для рендеринга контекста. --- **Примечание:** При использовании с представлением, которое использует сериализатор, `Response`, отправленный для рендеринга, может не быть словарем и должен быть обернут в `dict` перед возвратом, чтобы `TemplateHTMLRenderer` смог его отрендерить. Например: ```python response.data = {'results': response.data} ``` --- Имя шаблона определяется (в порядке предпочтения): 1. Явный аргумент `template_name`, передаваемый в ответ. 2. Явный атрибут `.template_name`, установленный для этого класса. 3. Результат вызова `view.get_template_names()`. Пример представления, использующего `TemplateHTMLRenderer`: ```python class UserDetail(generics.RetrieveAPIView): """ A view that returns a templated HTML representation of a given user. """ queryset = User.objects.all() renderer_classes = [TemplateHTMLRenderer] def get(self, request, *args, **kwargs): self.object = self.get_object() return Response({'user': self.object}, template_name='user_detail.html') ``` Вы можете использовать `TemplateHTMLRenderer` либо для возврата обычных HTML-страниц с помощью DRF, либо для возврата HTML и API ответов с одной конечной точки. Если вы создаете сайты, использующие `TemplateHTMLRenderer` наряду с другими классами рендереров, вам следует рассмотреть возможность включения `TemplateHTMLRenderer` в качестве первого класса в список `renderer_classes`, чтобы он был приоритетным даже для браузеров, которые посылают плохо сформированные заголовки `ACCEPT:`. Дополнительные примеры использования `TemplateHTMLRenderer` смотрите в [*HTML & Forms* Topic Page](../topics/html-and-forms.md). **.media_type**: `text/html`. **.format**: `'html'`. **.charset**: `utf-8` См. также: `StaticHTMLRenderer` ## StaticHTMLRenderer Простой рендерер, который просто возвращает предварительно отрендеренный HTML. В отличие от других рендереров, данные, передаваемые в объект ответа, должны быть строкой, представляющей возвращаемое содержимое. Пример представления, использующего `StaticHTMLRenderer`: ```python @api_view(['GET']) @renderer_classes([StaticHTMLRenderer]) def simple_html_view(request): data = '

Hello, world

' return Response(data) ``` Вы можете использовать `StaticHTMLRenderer` либо для возврата обычных HTML-страниц с помощью DRF, либо для возврата HTML- и API-ответов с одной конечной точки. **.media_type**: `text/html`. **.format**: `'html'`. **.charset**: `utf-8` См. также: `TemplateHTMLRenderer`. ## BrowsableAPIRenderer Рендерит данные в HTML для Browsable API: ![The BrowsableAPIRenderer](https://github.com/encode/django-rest-framework/raw/main/docs/img/quickstart.png) Этот рендерер определяет, какой другой рендерер имел бы наивысший приоритет, и использует его для отображения ответа в стиле API на HTML-странице. **.media_type**: `text/html`. **.format**: `'api'`. **.charset**: `utf-8` **.template**: `'rest_framework/api.html'`. #### Настройка BrowsableAPIRenderer По умолчанию содержимое ответа будет отображаться рендерером с наивысшим приоритетом, кроме `BrowsableAPIRenderer`. Если вам нужно настроить это поведение, например, использовать HTML в качестве формата возврата по умолчанию, но использовать JSON в Web-интерфейсе API, вы можете сделать это, переопределив метод `get_default_renderer()`. Например: ```python class CustomBrowsableAPIRenderer(BrowsableAPIRenderer): def get_default_renderer(self, view): return JSONRenderer() ``` ## AdminRenderer Рендерит данные в HTML для отображения в стиле администратора: ![Вид AdminRender](https://github.com/encode/django-rest-framework/raw/main/docs/img/quickstart.png) Этот рендерер подходит для веб-интерфейсов в стиле CRUD, которые также должны представлять удобный интерфейс для управления данными. Обратите внимание, что представления, которые имеют вложенные или списковые сериализаторы для своего ввода, не будут хорошо работать с `AdminRenderer`, так как HTML-формы не могут должным образом поддерживать их. --- **Примечание**: `AdminRenderer` способен включать ссылки на детальные страницы только в том случае, если в данных присутствует правильно настроенный атрибут `URL_FIELD_NAME` (по умолчанию `url`). Для `HyperlinkedModelSerializer` так и будет, но для классов `ModelSerializer` или простого `Serializer` вам нужно будет убедиться, что поле включено явно. Например, здесь мы используем метод модели `get_absolute_url`: ```python class AccountSerializer(serializers.ModelSerializer): url = serializers.CharField(source='get_absolute_url', read_only=True) class Meta: model = Account ``` --- **.media_type**: `text/html`. **.format**: `'admin'`. **.charset**: `utf-8` **.template**: `'rest_framework/admin.html'`. ## HTMLFormRenderer Переводит данные, возвращаемые сериализатором, в форму HTML. Вывод этого рендерера не включает заключающие теги `
`, скрытый CSRF-вход или какие-либо кнопки отправки. Этот рендерер не предназначен для прямого использования, но может быть использован в шаблонах путем передачи экземпляра сериализатора в тег шаблона `render_form`. ```html {% load rest_framework %} {% csrf_token %} {% render_form serializer %}
``` Для получения дополнительной информации смотрите документацию [HTML & Forms](../topics/html-and-forms.md). **.media_type**: `text/html`. **.format**: `'format'`. **.charset**: `utf-8` **.template**: `'rest_framework/horizontal/form.html'`. ## MultiPartRenderer Этот рендерер используется для рендеринга данных многочастной формы HTML. **Он не подходит для рендеринга ответов**, а используется для создания тестовых запросов, используя [тестовый клиент и фабрику тестовых запросов](testing.md) DRF. **.media_type**: `multipart/form-data; border=BoUnDaRyStRiNg`. **.format**: `'multipart'`. **.charset**: `utf-8` --- # Пользовательские рендеры Для реализации пользовательского рендерера необходимо отнаследоваться от `BaseRenderer`, установить свойства `.media_type` и `.format` и реализовать метод `.render(self, data, accepted_media_type=None, renderer_context=None)`. Метод должен возвращать строку байт, которая будет использоваться в качестве тела ответа HTTP. Аргументы, передаваемые методу `.render()`, следующие: ### `data` Данные запроса, заданные инстанцией `Response()`. ### `accepted_media_type=None` Дополнительно. Если указано, то это принятый тип носителя, определенный на этапе согласования содержимого. В зависимости от заголовка `Accept:` клиента, он может быть более конкретным, чем атрибут `media_type` рендерера, и может включать параметры типа медиа. Например, `'application/json; nested=true'`. ### `renderer_context=None` Опционально. Если предоставляется, то это словарь контекстной информации, предоставляемой представлением. По умолчанию сюда входят следующие ключи: `view`, `request`, `response`, `args`, `kwargs`. ## Пример Ниже приведен пример рендеринга обычного текста, который вернет ответ с параметром `data` в качестве содержимого ответа. ```python from django.utils.encoding import smart_str from rest_framework import renderers class PlainTextRenderer(renderers.BaseRenderer): media_type = 'text/plain' format = 'txt' def render(self, data, accepted_media_type=None, renderer_context=None): return smart_str(data, encoding=self.charset) ``` ## Установка набора символов По умолчанию предполагается, что классы рендеринга используют кодировку `UTF-8`. Чтобы использовать другую кодировку, установите атрибут `charset` для рендерера. ```python class PlainTextRenderer(renderers.BaseRenderer): media_type = 'text/plain' format = 'txt' charset = 'iso-8859-1' def render(self, data, accepted_media_type=None, renderer_context=None): return data.encode(self.charset) ``` Обратите внимание, что если класс рендерера возвращает строку unicode, то содержимое ответа будет преобразовано в строку байт классом `Response`, при этом атрибут `charset`, установленный на рендерере, будет использоваться для определения кодировки. Если рендерер возвращает строку байт, представляющую необработанное двоичное содержимое, вам следует установить значение charset равное `None`, что обеспечит отсутствие в заголовке `Content-Type` ответа значения `charset`. В некоторых случаях вы также можете установить атрибут `render_style` на `'binary'`. Это также гарантирует, что Web-интерфейс API не будет пытаться отобразить двоичное содержимое в виде строки. ```python class JPEGRenderer(renderers.BaseRenderer): media_type = 'image/jpeg' format = 'jpg' charset = None render_style = 'binary' def render(self, data, accepted_media_type=None, renderer_context=None): return data ``` --- # Расширенное использование рендеринга Вы можете делать довольно гибкие вещи, используя рендереры DRF. Некоторые примеры... * Предоставление плоских или вложенных представлений из одной и той же конечной точки, в зависимости от запрашиваемого типа носителя. * Предоставлять как обычные веб-страницы HTML, так и ответы API на основе JSON с одной и той же конечной точки. * Указывать несколько типов представления HTML для использования клиентами API. * Недоопределять медиатип рендерера, например, используя `media_type = 'image/*'`, и использовать заголовок `Accept` для изменения кодировки ответа. ## Различное поведение в зависимости от типа носителя В некоторых случаях вы можете захотеть, чтобы ваше представление использовало различные стили сериализации в зависимости от принятого типа носителя. Если вам нужно сделать это, вы можете обратиться к `request.accepted_renderer`, чтобы определить согласованный рендерер, который будет использоваться для ответа. Например: ```python @api_view(['GET']) @renderer_classes([TemplateHTMLRenderer, JSONRenderer]) def list_users(request): """ A view that can return JSON or HTML representations of the users in the system. """ queryset = Users.objects.filter(active=True) if request.accepted_renderer.format == 'html': # TemplateHTMLRenderer takes a context dict, # and additionally requires a 'template_name'. # It does not require serialization. data = {'users': queryset} return Response(data, template_name='list_users.html') # JSONRenderer requires serialized data as normal. serializer = UserSerializer(instance=queryset) data = serializer.data return Response(data) ``` ## Недоопределение типа носителя. В некоторых случаях вы можете захотеть, чтобы рендерер обслуживал различные типы медиа. В этом случае вы можете не указывать типы медиа, на которые он должен реагировать, используя значение `media_type`, такое как `image/*`, или `*/*`. Если вы недоопределили медиатип рендерера, вы должны убедиться, что указали медиатип явно, когда возвращаете ответ, используя атрибут `content_type`. Например: ```python return Response(data, content_type='image/png') ``` ## Проектирование типов носителей Для целей многих Web API может быть достаточно простых ответов `JSON` с гиперссылками на отношения. Если вы хотите полностью внедрить RESTful дизайн и [HATEOAS](http://timelessrepo.com/haters-gonna-hateoas), вам необходимо более детально продумать дизайн и использование типов медиа. По словам [Роя Филдинга](https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven), "REST API должен потратить почти все свои усилия по описанию на определение типа(ов) медиа, используемых для представления ресурсов и управления состоянием приложения, или на определение расширенных имен отношений и/или гипертекстовой разметки для существующих стандартных типов медиа". Хорошими примерами пользовательских типов медиа являются использование GitHub пользовательского типа медиа [application/vnd.github+json](https://developer.github.com/v3/media/) и одобренная IANA гипермедиа на основе JSON [application/vnd.collection+json](http://www.amundsen.com/media-types/collection/) Майка Амундсена. ## HTML представления ошибок Обычно рендерер ведет себя одинаково независимо от того, имеет ли он дело с обычным ответом или с ответом, вызванным возникшим исключением, например, исключением `Http404` или `PermissionDenied`, или подклассом `APIException`. Если вы используете `TemplateHTMLRenderer` или `StaticHTMLRenderer` и при этом возникает исключение, поведение немного отличается и является зеркальным отражением [Django's default handling of error views](https://docs.djangoproject.com/en/stable/topics/http/views/#customizing-error-views). Исключения, возникающие и обрабатываемые средством рендеринга HTML, будут пытаться отобразить с помощью одного из следующих методов, в порядке старшинства. * Загрузите и отобразите шаблон с именем `{status_code}.html`. * Загрузите и отобразите шаблон с именем `api_exception.html`. * Вывести код статуса HTTP и текст, например, "404 Not Found". Шаблоны будут отображаться с `RequestContext`, который включает ключи `status_code` и `details`. --- **Примечание**: Если `DEBUG=True`, то вместо отображения кода статуса HTTP и текста будет отображаться стандартная страница ошибки трассировки Django. --- # Пакеты сторонних производителей Также доступны следующие пакеты сторонних производителей. ## YAML [REST framework YAML](https://jpadilla.github.io/django-rest-framework-yaml/) обеспечивает поддержку разбора и рендеринга [YAML](http://www.yaml.org/). Ранее он был включен непосредственно в пакет DRF, но теперь поддерживается как сторонний пакет. #### Установка и настройка Установите с помощью pip. ```bash $ pip install djangorestframework-yaml ``` Измените настройки DRF. ```python REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': [ 'rest_framework_yaml.parsers.YAMLParser', ], 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework_yaml.renderers.YAMLRenderer', ], } ``` ## XML [REST Framework XML](https://jpadilla.github.io/django-rest-framework-xml/) предоставляет простой неформальный формат XML. Ранее он был включен непосредственно в пакет DRF, но теперь поддерживается как сторонний пакет. #### Установка и настройка Установите с помощью pip. ```bash $ pip install djangorestframework-xml ``` Измените настройки DRF. ```python REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': [ 'rest_framework_xml.parsers.XMLParser', ], 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework_xml.renderers.XMLRenderer', ], } ``` ## JSONP [REST framework JSONP](https://jpadilla.github.io/django-rest-framework-jsonp/) обеспечивает поддержку рендеринга JSONP. Ранее он был включен непосредственно в пакет DRF, но теперь поддерживается как сторонний пакет. --- **Предупреждение**: Если вам требуются междоменные AJAX-запросы, вам следует использовать более современный подход [CORS](https://www.w3.org/TR/cors/) в качестве альтернативы `JSONP`. Более подробную информацию смотрите в документации [CORS](https://www.django-rest-framework.org/topics/ajax-csrf-cors/). Подход `jsonp` по сути является хаком для браузера и подходит [только для глобально читаемых конечных точек API](https://stackoverflow.com/questions/613962/is-jsonp-safe-to-use), где запросы `GET` являются неаутентифицированными и не требуют никаких разрешений пользователя. --- #### Установка и настройка Установите с помощью pip. ```bash $ pip install djangorestframework-jsonp ``` Измените настройки DRF. ```python REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework_jsonp.renderers.JSONPRenderer', ], } ``` ## MessagePack [MessagePack](https://msgpack.org/) - это быстрый и эффективный формат двоичной сериализации. [Juan Riaza](https://github.com/juanriaza) поддерживает пакет [djangorestframework-msgpack](https://github.com/juanriaza/django-rest-framework-msgpack), который обеспечивает поддержку рендеринга и парсера MessagePack для DRF. ## Microsoft Excel: XLSX (Двоичные конечные точки электронных таблиц) XLSX - это самый популярный в мире формат двоичных электронных таблиц. [Тим Аллен](https://github.com/flipperpa) из [The Wharton School](https://github.com/wharton) поддерживает [drf-excel](https://github.com/wharton/drf-excel), который отображает конечную точку в виде электронной таблицы XLSX с помощью OpenPyXL и позволяет клиенту загрузить ее. Электронные таблицы могут быть стилизованы для каждого вида. #### Установка и настройка Установите с помощью pip. ```bash $ pip install drf-excel ``` Измените настройки DRF. ```python REST_FRAMEWORK = { ... 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', 'drf_excel.renderers.XLSXRenderer', ], } ``` Чтобы избежать потоковой передачи файла без имени (при этом браузер часто принимает по умолчанию имя файла "download" без расширения), нам необходимо использовать миксин для переопределения заголовка `Content-Disposition`. Если имя файла не указано, то по умолчанию будет задано `export.xlsx`. Например: ```python from rest_framework.viewsets import ReadOnlyModelViewSet from drf_excel.mixins import XLSXFileMixin from drf_excel.renderers import XLSXRenderer from .models import MyExampleModel from .serializers import MyExampleSerializer class MyExampleViewSet(XLSXFileMixin, ReadOnlyModelViewSet): queryset = MyExampleModel.objects.all() serializer_class = MyExampleSerializer renderer_classes = [XLSXRenderer] filename = 'my_export.xlsx' ``` ## CSV Значения, разделенные запятыми, - это формат табличных данных в виде обычного текста, который легко импортируется в приложения электронных таблиц. [Mjumbe Poe](https://github.com/mjumbewu) поддерживает пакет [djangorestframework-csv](https://github.com/mjumbewu/django-rest-framework-csv), который обеспечивает поддержку CSV-рендеринга для DRF. ## UltraJSON [UltraJSON](https://github.com/esnme/ultrajson) - это оптимизированный кодировщик JSON на языке C, который может значительно ускорить рендеринг JSON. [Adam Mertz](https://github.com/Amertz08) поддерживает [drf_ujson2](https://github.com/Amertz08/drf_ujson2), форк ныне не поддерживаемого [drf-ujson-renderer](https://github.com/gizmag/drf-ujson-renderer), который реализует рендеринг JSON с использованием пакета UJSON. ## CamelCase JSON [djangorestframework-camel-case](https://github.com/vbabiy/djangorestframework-camel-case) предоставляет рендереры и парсеры JSON в верблюжьем регистре для DRF. Это позволяет сериализаторам использовать имена полей в стиле Python с подчеркиванием, но отображать их в API как имена полей в верблюжьем регистре в стиле Javascript. Поддерживается [Виталием Бабием](https://github.com/vbabiy). ## Pandas (CSV, Excel, PNG) [Django REST Pandas](https://github.com/wq/django-rest-pandas) предоставляет сериализатор и рендереры, которые поддерживают дополнительную обработку и вывод данных через [Pandas](https://pandas.pydata.org/) DataFrame API. Django REST Pandas включает рендереры для файлов CSV в стиле Pandas, рабочих книг Excel (как `.xls`, так и `.xlsx`) и ряда [других форматов](https://github.com/wq/django-rest-pandas#supported-formats). Он поддерживается [S. Andrew Sheppard](https://github.com/sheppard) в рамках проекта [wq Project](https://github.com/wq). ## LaTeX [Rest Framework Latex](https://github.com/mypebble/rest-framework-latex) предоставляет рендерер, который выводит PDF-файлы с использованием Lualatex. Он поддерживается [Pebble (S/F Software)](https://github.com/mypebble). ================================================ FILE: api-guide/requests.md ================================================ # Запросы > Если вы занимаетесь веб-сервисами на основе REST... вам следует игнорировать request.POST. > > - Malcom Tredinnick, [Django developers group](https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion) Класс `Request` DRF расширяет стандартный `HttpRequest`, добавляя поддержку гибкого разбора запросов DRF и аутентификации запросов. --- # Разбор запроса Объекты Request DRF обеспечивают гибкий разбор запросов, что позволяет обрабатывать запросы с данными JSON или другими типами медиа таким же образом, как вы обычно обрабатываете данные формы. ## .data `request.data` возвращает разобранное содержимое тела запроса. Это похоже на стандартные атрибуты `request.POST` и `request.FILES`, за исключением того, что: * Он включает в себя все разобранное содержимое, включая *файловые и нефайловые* входы. * Поддерживается разбор содержимого методов HTTP, отличных от `POST`, что означает, что вы можете получить доступ к содержимому запросов `PUT` и `PATCH`. * Поддерживается гибкий разбор запросов DRF, а не только поддержка данных формы. Например, вы можете обрабатывать входящие [JSON-данные](parsers.md#jsonparser) аналогично тому, как вы обрабатываете входящие [данные формы](parsers.md#formparser). Более подробную информацию можно найти в [документации по парсерам](parsers.md). ## .query_params `request.query_params` - это более правильно названный синоним `request.GET`. Для ясности внутри вашего кода мы рекомендуем использовать `request.query_params` вместо стандартного для Django `request.GET`. Это поможет сохранить вашу кодовую базу более корректной и очевидной - любой тип HTTP метода может включать параметры запроса, а не только `GET` запросы. ## .parsers Класс `APIView` или декоратор `@api_view` обеспечат автоматическую установку этого свойства в список экземпляров `Parser` на основе `parser_classes`, установленных в представлении, или на основе настройки `DEFAULT_PARSER_CLASSES`. Обычно вам не требуется доступ к этой собственности. --- **Примечание:** Если клиент посылает недостоверное содержимое, то при доступе к `request.data` может возникнуть ошибка `ParseError`. По умолчанию класс `APIView` или декоратор `@api_view` DRF поймает ошибку и вернет ответ `400 Bad Request`. Если клиент посылает запрос с типом содержимого, который не может быть разобран, то возникает исключение `UnsupportedMediaType`, которое по умолчанию будет поймано и вернет ответ `415 Unsupported Media Type`. --- # Переговоры по содержанию Запрос раскрывает некоторые свойства, которые позволяют определить результат этапа согласования содержимого. Это позволяет вам реализовать такое поведение, как выбор различных схем сериализации для различных типов носителей. ## .accepted_renderer Экземпляр рендерера, который был выбран на этапе согласования содержимого. ## .accepted_media_type Строка, представляющая тип носителя, который был принят на этапе согласования содержимого. --- # Аутентификация DRF обеспечивает гибкую аутентификацию по каждому запросу, что дает вам возможность: * Используйте различные политики аутентификации для разных частей вашего API. * Поддерживайте использование нескольких политик аутентификации. * Предоставлять информацию о пользователе и маркере, связанную с входящим запросом. ## .user `request.user` обычно возвращает экземпляр `django.contrib.auth.models.User`, хотя поведение зависит от используемой политики аутентификации. Если запрос не аутентифицирован, значением по умолчанию для `request.user` будет экземпляр `django.contrib.auth.models.AnonymousUser`. Более подробную информацию можно найти в [документации по аутентификации](authentication.md). ## .auth `request.auth` возвращает любой дополнительный контекст аутентификации. Точное поведение `request.auth` зависит от используемой политики аутентификации, но обычно это может быть экземпляр токена, по которому был аутентифицирован запрос. Если запрос не аутентифицирован, или если отсутствует дополнительный контекст, значение по умолчанию `request.auth` равно `None`. Более подробную информацию можно найти в [документации по аутентификации](authentication.md). ## .authenticators Класс `APIView` или декоратор `@api_view` обеспечат автоматическую установку этого свойства в список экземпляров `Authentication` на основе `authentication_classes`, установленных для представления, или на основе параметра `DEFAULT_AUTHENTICATORS`. Обычно вам не требуется доступ к этой собственности. --- **Примечание:** При вызове свойств `.user` или `.auth` может возникнуть ошибка `WrappedAttributeError`. Эти ошибки исходят от аутентификатора как стандартные `AttributeError`, однако необходимо, чтобы они были повторно вызваны как исключение другого типа, чтобы предотвратить их подавление внешним доступом к свойству. Python не распознает, что `AttributeError` исходит от аутентификатора, и вместо этого будет считать, что объект запроса не имеет свойства `.user` или `.auth`. Аутентификатор необходимо будет исправить. --- # Улучшения в браузере DRF поддерживает несколько улучшений для браузеров, таких как браузерные формы `PUT`, `PATCH` и `DELETE`. ## .method `request.method` возвращает **упрощенное** строковое представление HTTP-метода запроса. Формы `PUT`, `PATCH` и `DELETE`, основанные на браузере, поддерживаются прозрачно. Для получения дополнительной информации см. документацию [browser enhancements documentation](../topics/browser-enhancements.md). ## .content_type `request.content_type`, возвращает строковый объект, представляющий тип медиа тела HTTP запроса, или пустую строку, если тип медиа не был предоставлен. Обычно вам не нужно напрямую обращаться к типу содержимого запроса, поскольку вы обычно полагаетесь на стандартное поведение DRF при разборе запроса. Если вам необходимо получить доступ к типу содержимого запроса, вам следует использовать свойство `.content_type`, а не `request.META.get('HTTP_CONTENT_TYPE')`, так как оно обеспечивает прозрачную поддержку неформенного содержимого в браузере. Для получения дополнительной информации см. документацию [browser enhancements documentation](../topics/browser-enhancements.md). ## .stream `request.stream` возвращает поток, представляющий содержимое тела запроса. Как правило, вам не понадобится прямой доступ к содержимому запроса, поскольку вы обычно полагаетесь на стандартное поведение DRF при разборе запроса. --- # Стандартные атрибуты HttpRequest Поскольку `Request` DRF расширяет `HttpRequest` фреймворка Django, все остальные стандартные атрибуты и методы также доступны. Например, словари `request.META` и `request.session` доступны как обычно. Обратите внимание, что по причинам реализации класс `Request` не наследуется от класса `HttpRequest`, а вместо этого расширяет его, используя композицию. ================================================ FILE: api-guide/responses.md ================================================ # Ответы > В отличие от базовых объектов HttpResponse, объекты TemplateResponse сохраняют детали контекста, который был предоставлен представлением для вычисления ответа. Окончательный результат ответа не вычисляется до тех пор, пока он не понадобится, позже в процессе ответа. > > - [Django documentation](https://docs.djangoproject.com/en/stable/ref/template-response/) DRF поддерживает согласование содержимого HTTP, предоставляя класс `Response`, который позволяет возвращать содержимое, которое может быть преобразовано в несколько типов содержимого, в зависимости от запроса клиента. Класс `Response` является подклассом Django `SimpleTemplateResponse`. Объекты `Response` инициализируются данными, которые должны состоять из собственных примитивов Python. Затем DRF использует стандартное согласование содержимого HTTP для определения того, как он должен отображать конечное содержимое ответа. Нет необходимости использовать класс `Response`, при необходимости вы можете возвращать обычные объекты `HttpResponse` или `StreamingHttpResponse` из ваших представлений. Использование класса `Response` просто предоставляет более удобный интерфейс для возврата ответов Web API, согласованных по содержанию, которые могут быть преобразованы в различные форматы. Если вы по каким-то причинам не хотите сильно кастомизировать DRF, вы всегда должны использовать класс `APIView` или функцию `@api_view` для представлений, которые возвращают объекты `Response`. Это гарантирует, что представление сможет выполнить согласование содержимого и выбрать подходящий рендерер для ответа, прежде чем он будет возвращен из представления. --- # Создание ответов ## Response() **Сигнатура:** `Response(data, status=None, template_name=None, headers=None, content_type=None)`. В отличие от обычных объектов `HttpResponse`, вы не создаете объекты `Response` со сформированным содержимым. Вместо этого вы передаете несформированные данные, которые могут состоять из любых примитивов Python. Рендереры, используемые классом `Response`, не могут нативно обрабатывать сложные типы данных, такие как экземпляры моделей Django, поэтому вам необходимо сериализовать данные в примитивные типы данных перед созданием объекта `Response`. Вы можете использовать классы DRF `Serializer` для выполнения этой сериализации данных или использовать свою собственную сериализацию. Аргументы: * `data`: Сериализованные данные для ответа. * `status`: Код статуса для ответа. По умолчанию 200. См. также [коды статуса](status-codes.md). * `template_name`: Имя шаблона, который будет использоваться, если выбран `HTMLRenderer`. * `headers`: Словарь заголовков HTTP для использования в ответе. * `content_type`: Тип содержимого ответа. Как правило, он устанавливается автоматически рендерером в результате согласования содержимого, но в некоторых случаях может потребоваться явное указание типа содержимого. --- # Атрибуты ## .data Несформированные, сериализованные данные ответа. ## .status_code Цифровой код состояния ответа HTTP. ## .content Сформированное содержимое ответа. Метод `.render()` должен быть вызван, прежде чем можно будет получить доступ к `.content`. ## .template_name Файл шаблона `template_name`, если он задан. Требуется, только если `HTMLRenderer` или другой пользовательский рендерер шаблона является применимым рендерером для ответа. ## .accepted_renderer Экземпляр рендерера, который будет использоваться для формирования ответа. Устанавливается автоматически `APIView` или `@api_view` непосредственно перед возвратом ответа из представления. ## .accepted_media_type Тип документа, который был выбран на этапе согласования контента. Устанавливается автоматически `APIView` или `@api_view` непосредственно перед возвратом ответа из представления. ## .renderer_context Словарь дополнительной контекстной информации, которая будет передана в метод `.render()` рендерера. Устанавливается автоматически `APIView` или `@api_view` непосредственно перед возвратом ответа из представления. --- # Стандартные атрибуты HttpResponse Класс `Response` расширяет `SimpleTemplateResponse`, и все обычные атрибуты и методы также доступны для ответа. Например, вы можете установить заголовки для ответа стандартным способом: ```python response = Response() response['Cache-Control'] = 'no-cache' ``` ## .render() **Сигнатура:** `.render()`. Как и любой другой `TemplateResponse`, этот метод вызывается для преобразования сериализованных данных ответа в конечное содержимое ответа. Когда вызывается `.render()`, содержимое ответа будет установлено в результат вызова метода `.render(data, accepted_media_type, renderer_context)` на экземпляре `accepted_renderer`. Обычно вам не нужно вызывать `.render()` самостоятельно, так как это обрабатывается стандартным циклом ответа Django. ================================================ FILE: api-guide/reverse.md ================================================ --- источник: - reverse.py --- # Возвращение URL-адресов > Центральной особенностью, отличающей архитектурный стиль REST от других сетевых стилей, является его акцент на едином интерфейсе между компонентами. > > — Рой Филдинг, [Архитектурные стили и проектирование сетевых архитектур программного обеспечения](https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_5) Как правило, лучше возвращать абсолютные URI из ваших Web API, например `http://example.com/foobar`, а не относительные URI, например `/foobar`. Преимуществами этого являются: * Это более четко выражено. * Это оставляет меньше работы для ваших клиентов API. * Нет двусмысленности относительно значения строки, когда она встречается в таких форматах, как JSON, которые не имеют собственного типа URI. * Это упрощает такие вещи, как разметка HTML-представлений с гиперссылками. Фреймворк REST предоставляет две служебные функции для упрощения возврата абсолютных URI из вашего Web API. Использовать их не обязательно, но если вы их используете, то самоописывающийся API сможет автоматически делать для вас гиперссылки на свой вывод, что значительно упростит просмотр API. ## reverse **Сигнатура:** `reverse(viewname, *args, **kwargs)`. Имеет такое же поведение, как [`django.urls.reverse`](https://docs.djangoproject.com/en/stable/ref/urlresolvers/#reverse), за исключением того, что возвращает полностью определенный URL, используя запрос для определения хоста и порта. Вы должны **включить запрос в качестве именованного аргумента** в функцию, например: ```python from rest_framework.reverse import reverse from rest_framework.views import APIView from django.utils.timezone import now class APIRootView(APIView): def get(self, request): year = now().year data = { ... 'year-summary-url': reverse('year-summary', args=[year], request=request) } return Response(data) ``` ## reverse_lazy **Сигнатура:** `reverse_lazy(viewname, *args, **kwargs)`. Имеет такое же поведение, как [`django.urls.reverse_lazy`](https://docs.djangoproject.com/en/stable/ref/urlresolvers/#reverse-lazy), за исключением того, что возвращает полностью определенный URL, используя запрос для определения хоста и порта. Как и в случае с функцией `reverse`, вы должны **включить запрос в качестве именованного аргумента** в функцию, например: ```python api_root = reverse_lazy('api-root', request=request) ``` ================================================ FILE: api-guide/routers.md ================================================ --- источник: - routers.py --- # Маршрутизаторы > Маршрутизация ресурсов позволяет быстро объявить все общие маршруты для данного ресурсного контроллера. Вместо объявления отдельных маршрутов для вашего индекса... ресурсный маршрут объявляет их в одной строке кода. > > — [Ruby on Rails Documentation](https://guides.rubyonrails.org/routing.html) Некоторые веб-фреймворки, такие как Rails, предоставляют функциональность для автоматического определения того, как URL-адреса приложения должны быть сопоставлены с логикой, которая занимается обработкой входящих запросов. DRF добавляет в Django поддержку автоматической маршрутизации URL и предоставляет вам простой, быстрый и последовательный способ подключения логики представления к набору URL. ## Использование Вот пример простого URL conf, который использует `SimpleRouter`. ```python from rest_framework import routers router = routers.SimpleRouter() router.register(r'users', UserViewSet) router.register(r'accounts', AccountViewSet) urlpatterns = router.urls ``` У метода `register()` есть два обязательных аргумента: * `prefix` - Префикс URL, который будет использоваться для этого набора маршрутов. * `viewset` - Класс набора представлений. По желанию вы можете указать дополнительный аргумент: * `basename` - Основа, которую следует использовать для создаваемых имен URL. Если значение не задано, то базовое имя будет автоматически генерироваться на основе атрибута `queryset` набора представлений, если он есть. Обратите внимание, что если набор представлений не включает атрибут `queryset`, то вы должны установить `basename` при регистрации набора представлений. В приведенном выше примере будут сгенерированы следующие шаблоны URL: * Шаблон URL: `^users/$` Имя: `'user-list'` * Шаблон URL: `^users/{pk}/$` Имя: `'user-detail'` * Шаблон URL: `^accounts/$` Имя: `'account-list'` * Шаблон URL: `^accounts/{pk}/$` Имя: `'account-detail'` --- **Примечание**: Аргумент `basename` используется для указания начальной части шаблона имени представления. В приведенном выше примере это часть `user` или `account`. Обычно вам не нужно указывать аргумент `basename`, но если у вас есть набор представлений, в котором вы определили пользовательский метод `get_queryset`, то набор представлений может не иметь атрибута `.queryset`. Если вы попытаетесь зарегистрировать этот набор представлений, вы увидите ошибку, подобную этой: ```text 'basename' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.queryset' attribute. ``` Это означает, что вам нужно будет явно задать аргумент `basename` при регистрации набора представлений, поскольку он не может быть автоматически определен из имени модели. --- ### Использование `include` с маршрутизаторами Атрибут `.urls` экземпляра маршрутизатора - это просто стандартный список шаблонов URL. Существует несколько различных стилей для включения этих URL. Например, вы можете добавить `router.urls` к списку существующих представлений... ```python router = routers.SimpleRouter() router.register(r'users', UserViewSet) router.register(r'accounts', AccountViewSet) urlpatterns = [ path('forgot-password/', ForgotPasswordFormView.as_view()), ] urlpatterns += router.urls ``` В качестве альтернативы вы можете использовать функцию Django `include`, например, так... ```python urlpatterns = [ path('forgot-password', ForgotPasswordFormView.as_view()), path('', include(router.urls)), ] ``` Вы можете использовать `include` с пространством имен приложения: ```python urlpatterns = [ path('forgot-password/', ForgotPasswordFormView.as_view()), path('api/', include((router.urls, 'app_name'))), ] ``` Или как пространство имен приложения и экземпляра: ```python urlpatterns = [ path('forgot-password/', ForgotPasswordFormView.as_view()), path('api/', include((router.urls, 'app_name'), namespace='instance_name')), ] ``` Более подробную информацию смотрите в документации Django [URL namespaces docs](https://docs.djangoproject.com/en/stable/topics/http/urls/#url-namespaces) и в [`include` API reference](https://docs.djangoproject.com/en/stable/ref/urls/#include). --- **Примечание**: При использовании пространства имен с гиперссылками в сериализаторах вам также необходимо убедиться, что любые параметры `view_name` в сериализаторах правильно отражают пространство имен. В примерах выше вам нужно будет включить параметр типа `view_name='app_name:user-detail'` для полей сериализатора, гиперссылка на представление подробных данных пользователя. Для автоматического создания `view_name` используется шаблон типа `%(имя_модели)-detail`. Если только имена ваших моделей не противоречат друг другу, вам, возможно, будет лучше **не** расставлять имена в представлениях DRF при использовании сериализаторов с гиперссылками. --- ### Маршрутизация для дополнительных действий Набор представлений может [пометить дополнительные действия для маршрутизации](viewsets.md#добавление-дополнительных-действий-в-маршрутизацию), украсив метод декоратором `@action`. Эти дополнительные действия будут включены в сгенерированные маршруты. Например, дан метод `set_password` для класса `UserViewSet`: ```python from myapp.permissions import IsAdminOrIsSelf from rest_framework.decorators import action class UserViewSet(ModelViewSet): ... @action(methods=['post'], detail=True, permission_classes=[IsAdminOrIsSelf]) def set_password(self, request, pk=None): ... ``` Будет создан следующий маршрут: * Шаблон URL: `^users/{pk}/set_password/$` * Имя URL: `'user-set-password'`. По умолчанию шаблон URL основан на имени метода, а имя URL представляет собой комбинацию `ViewSet.basename` и имени метода через дефис. Если вы не хотите использовать значения по умолчанию, вы можете указать аргументы `url_path` и `url_name` в декораторе `@action`. Например, если вы хотите изменить URL для нашего пользовательского действия на `^users/{pk}/change-password/$`, вы можете написать: ```python from myapp.permissions import IsAdminOrIsSelf from rest_framework.decorators import action class UserViewSet(ModelViewSet): ... @action(methods=['post'], detail=True, permission_classes=[IsAdminOrIsSelf], url_path='change-password', url_name='change_password') def set_password(self, request, pk=None): ... ``` Приведенный выше пример теперь будет генерировать следующий шаблон URL: * URL путь: `^users/{pk}/change-password/$` * Имя URL: `'user-change_password'`. ### Использование Django `path()` с маршрутизаторами По умолчанию URL, создаваемые маршрутизаторами, используют регулярные выражения. Это поведение можно изменить, установив аргумент `use_regex_path` в `False` при инстанцировании маршрутизатора, в этом случае будут использоваться [преобразователи путей](https://docs.djangoproject.com/en/stable/topics/http/urls/#path-converters). Например: ```python router = SimpleRouter(use_regex_path=False) ``` Маршрутизатор будет соответствовать значениям поиска, содержащим любые символы, кроме косой черты и точки. Чтобы получить более строгий (или более мягкий) шаблон поиска, установите атрибут `lookup_value_regex` в наборе представлений или `lookup_value_converter` при использовании конвертеров путей. Например, вы можете ограничить поиск допустимыми UUID: ```python class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): lookup_field = 'my_model_id' lookup_value_regex = '[0-9a-f]{32}' class MyPathModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): lookup_field = 'my_model_uuid' lookup_value_converter = 'uuid' ``` Обратите внимание, что преобразователи путей будут использоваться для всех URL, зарегистрированных в маршрутизаторе, включая действия набора представлений. # Руководство по API ## SimpleRouter Этот маршрутизатор включает маршруты для стандартного набора действий `list`, `create`, `retrieve`, `update`, `partial_update` и `destroy`. Набор представлений также может отметить дополнительные методы для маршрутизации, используя декоратор `@action`. | URL Style | HTTP Method | Action | URL Name | | ----------------------------- | ------------------------------------------ | ------------------------------------------ | --------------------- | | {prefix}/ | GET | list | {basename}-list | | | POST | create | | | {prefix}/{url_path}/ | GET, или как указано в аргументе `methods` | метод с декоратором `@action(detail=False)`| {basename}-{url_name} | | {prefix}/{lookup}/ | GET | retrieve | {basename}-detail | | | PUT | update | | | | PATCH | partial_update | | | | DELETE | destroy | | | {prefix}/{lookup}/{url_path}/ | GET, или как указано в аргументе `methods` | метод с декоратором `@action(detail=True)` | {basename}-{url_name} | По умолчанию URL, создаваемые `SimpleRouter`, дополняются косой чертой. Это поведение можно изменить, установив аргумент `trailing_slash` в `False` при инстанцировании маршрутизатора. Например: ```python router = SimpleRouter(trailing_slash=False) ``` ## DefaultRouter Этот маршрутизатор похож на `SimpleRouter`, но дополнительно включает корневое представление API по умолчанию, которое возвращает ответ, содержащий гиперссылки на все представления списка. Он также генерирует маршруты для необязательных суффиксов формата `.json`. | URL Style | HTTP Method | Action | URL Name | | -------------------------------------- | ------------------------------------------ | ---------------------------------------------------- | --------------------- | | [.format] | GET | автоматически сгенерированное корневое представление | api-root | | {prefix}/[.format] | GET | list | {basename}-list | | | POST | create | | | {prefix}/{url_path}/[.format] | GET, или как указано в аргументе `methods` | метод с декоратором `@action(detail=False)` | {basename}-{url_name} | | {prefix}/{lookup}/[.format] | GET | retrieve | {basename}-detail | | | PUT | update | | | | PATCH | partial_update | | | | DELETE | destroy | | | {prefix}/{lookup}/{url_path}/[.format] | GET, или как указано в аргументе `methods` | метод с декоратором `@action(detail=True)` | {basename}-{url_name} | Как и в `SimpleRouter`, косые черты в маршрутах URL могут быть удалены путем установки аргумента `trailing_slash` в `False` при инстанцировании маршрутизатора. ```python router = DefaultRouter(trailing_slash=False) ``` # Пользовательские маршрутизаторы Реализация пользовательского маршрутизатора - это не то, что вам нужно делать очень часто, но это может быть полезно, если у вас есть особые требования к структуре URL для вашего API. Это позволит вам инкапсулировать структуру URL в многократно используемый способ, который гарантирует, что вам не придется писать шаблоны URL в явном виде для каждого нового представления. Самый простой способ реализации пользовательского маршрутизатора - это подкласс одного из существующих классов маршрутизаторов. Атрибут `.routes` используется для шаблонизации шаблонов URL, которые будут сопоставлены с каждым набором представлений. Атрибут `.routes` представляет собой список кортежей с именем `Route`. Аргументами кортежа с именем `Route` являются: **url**: Строка, представляющая URL, который должен быть маршрутизирован. Может включать следующие строки формата: * `{prefix}` - Префикс URL, который будет использоваться для этого набора маршрутов. * `{lookup}` - Поле поиска, используемое для сопоставления с одним экземпляром. * `{trailing_slash}` - Либо '/', либо пустая строка, в зависимости от аргумента `trailing_slash`. **mapping**: Сопоставление имен методов HTTP с методами представления **name**: Имя URL, используемое в вызовах `reverse`. Может включать следующую строку формата: * `{basename}` - Основа, которую следует использовать для создаваемых имен URL. **initkwargs**: Словарь дополнительных аргументов, которые должны быть переданы при инстанцировании представления. Обратите внимание, что аргументы `detail`, `basename` и `suffix` зарезервированы для интроспекции набора представлений и также используются API просмотра для генерации имени представления и ссылок на хлебные крошки. ## Настройка динамических маршрутов Вы также можете настроить способ маршрутизации декоратора `@action`. Включите кортеж с именем `DynamicRoute` в список `.routes`, установив аргумент `detail` в соответствии с требованиями для маршрутов на основе списка и на основе деталей. В дополнение к `detail`, аргументами `DynamicRoute` являются: **url**: Строка, представляющая URL, который должен быть маршрутизирован. Может включать те же строки формата, что и `Route`, и дополнительно принимает строку формата `{url_path}`. **name**: Имя URL, используемое в вызовах `reverse`. Может включать следующие строки формата: * `{basename}` - Основа, которую следует использовать для создаваемых имен URL. * `{url_name}` - `имя URL`, предоставляемое `@action`. **initkwargs**: Словарь любых дополнительных аргументов, которые должны быть переданы при инстанцировании представления. ## Пример Следующий пример маршрутизирует только действия `list` и `retrieve` и не использует соглашение о косой черте. ```python from rest_framework.routers import Route, DynamicRoute, SimpleRouter class CustomReadOnlyRouter(SimpleRouter): """ A router for read-only APIs, which doesn't use trailing slashes. """ routes = [ Route( url=r'^{prefix}$', mapping={'get': 'list'}, name='{basename}-list', detail=False, initkwargs={'suffix': 'List'} ), Route( url=r'^{prefix}/{lookup}$', mapping={'get': 'retrieve'}, name='{basename}-detail', detail=True, initkwargs={'suffix': 'Detail'} ), DynamicRoute( url=r'^{prefix}/{lookup}/{url_path}$', name='{basename}-{url_name}', detail=True, initkwargs={} ) ] ``` Давайте посмотрим на маршруты, которые наш `CustomReadOnlyRouter` будет генерировать для простого набора представлений. `views.py`: ```python class UserViewSet(viewsets.ReadOnlyModelViewSet): """ A viewset that provides the standard actions """ queryset = User.objects.all() serializer_class = UserSerializer lookup_field = 'username' @action(detail=True) def group_names(self, request, pk=None): """ Returns a list of all the group names that the given user belongs to. """ user = self.get_object() groups = user.groups.all() return Response([group.name for group in groups]) ``` `urls.py`: ```python router = CustomReadOnlyRouter() router.register('users', UserViewSet) urlpatterns = router.urls ``` Будут созданы следующие отображения... | URL | HTTP Method | Action | URL Name | | ----------------------------- | ----------- | ----------- | ---------------- | | /users | GET | list | user-list | | /users/{username} | GET | retrieve | user-detail | | /users/{username}/group_names | GET | group_names | user-group-names | Другой пример установки атрибута `.routes` приведен в исходном коде класса `SimpleRouter`. ## Расширенные пользовательские маршрутизаторы Если вы хотите обеспечить полностью пользовательское поведение, вы можете переопределить `BaseRouter` и переопределить метод `get_urls(self)`. Метод должен проверить зарегистрированные наборы представлений и вернуть список шаблонов URL. Зарегистрированные кортежи префикса, набора представлений и базового имени можно проверить, обратившись к атрибуту `self.registry`. Вы также можете переопределить метод `get_default_basename(self, viewset)` или всегда явно задавать аргумент `basename` при регистрации ваших наборов представлений в маршрутизаторе. # Сторонние пакеты Также доступны следующие пакеты сторонних производителей. ## DRF Nested Routers Пакет [drf-nested-routers](https://github.com/alanjds/drf-nested-routers) предоставляет маршрутизаторы и поля отношений для работы с вложенными ресурсами. ## ModelRouter (wq.db.rest) Пакет [wq.db](https://wq.io/wq.db) предоставляет расширенный класс [ModelRouter](https://wq.io/docs/router) (и экземпляр синглтона), который расширяет `DefaultRouter` с API `register_model()`. Подобно Django's `admin.site.register`, единственным необходимым аргументом для `rest.router.register_model` является класс модели. Разумные значения по умолчанию для префикса url, сериализатора и набора представлений будут определяться из модели и глобальной конфигурации. ```python from wq.db import rest from myapp.models import MyModel rest.router.register_model(MyModel) ``` ## DRF-extensions Пакет [`DRF-extensions`](https://chibisov.github.io/drf-extensions/docs/) предоставляет [маршрутизаторы](https://chibisov.github.io/drf-extensions/docs/#routers) для создания [вложенных наборов представлений](https://chibisov.github.io/drf-extensions/docs/#nested-routes), [контроллеров уровня коллекции](https://chibisov.github.io/drf-extensions/docs/#collection-level-controllers) с [настраиваемыми именами конечных точек](https://chibisov.github.io/drf-extensions/docs/#controller-endpoint-name). ================================================ FILE: api-guide/schemas.md ================================================ # Schemas > Машиночитаемая \[схема\] описывает, какие ресурсы доступны через API, каковы их URL, как они представлены и какие операции они поддерживают. > > - Heroku, [JSON Schema for the Heroku Platform API](https://www.heroku.com/blog/json_schema_for_heroku_platform_api/) --- **Уведомление о сокращении:** Встроенная в DRF поддержка генерации схем OpenAPI **утрачена** в пользу сторонних пакетов, которые могут предоставить эту функциональность вместо нее. Встроенная поддержка будет перенесена в отдельный пакет, а затем в последующих релизах будет удалена. В качестве полноценной замены мы рекомендуем пакет [drf-spectacular](https://drf-spectacular.readthedocs.io/en/latest/readme.html). Он обладает широкой поддержкой генерации схем OpenAPI 3 из API DRF, причем доступны как автоматические, так и настраиваемые опции. За дополнительной информацией обращайтесь к [Documenting your API](../topics/documenting-your-api.md#drf-spectacular). --- Схемы API - это полезный инструмент, который позволяет использовать их в различных случаях, включая создание справочной документации или создание динамических клиентских библиотек, которые могут взаимодействовать с вашим API. DRF обеспечивает поддержку автоматической генерации схем [OpenAPI](https://github.com/OAI/OpenAPI-Specification). ## Обзор Генерация схемы состоит из нескольких движущихся частей. Стоит сделать обзор: * `SchemaGenerator` - это класс верхнего уровня, который отвечает за поиск шаблонов URL, нахождение подклассов `APIView`, запрос их представления схемы и компиляцию конечного объекта схемы. * `AutoSchema` инкапсулирует все детали, необходимые для интроспекции схемы для каждого представления. Прикрепляется к каждому представлению через атрибут `schema`. Вы подклассифицируете `AutoSchema` для того, чтобы настроить свою схему. * Команда управления `generateschema` позволяет вам генерировать статическую схему в автономном режиме. * Альтернативно, вы можете направить `SchemaView` для динамической генерации и обслуживания вашей схемы. * `settings.DEFAULT_SCHEMA_CLASS` позволяет вам указать подкласс `AutoSchema`, который будет использоваться по умолчанию в вашем проекте. В следующих разделах рассказывается подробнее. ## Генерация схемы OpenAPI ### Установите зависимости ```bash pip install pyyaml uritemplate inflection ``` * `pyyaml` используется для генерации схемы в формат OpenAPI на основе YAML. * `uritemplate` используется для получения параметров в пути. * `inflection` для более подходящего множественного числа операций в конечных точках списка. ### Генерация статической схемы с помощью команды управления `generateschema`. Если ваша схема статична, вы можете использовать команду управления `generateschema`: ```bash ./manage.py generateschema --file openapi-schema.yml ``` После создания схемы таким образом вы можете аннотировать ее любой дополнительной информацией, которая не может быть автоматически выведена генератором схемы. Вы можете зарегистрировать схему API в системе контроля версий и обновлять ее с каждым новым релизом, или использовать схему API из статического медиа вашего сайта. ### Генерация динамической схемы с помощью `SchemaView`. Если вам нужна динамическая схема, например, потому что выбор внешнего ключа зависит от значений базы данных, вы можете создать `SchemaView`, который будет генерировать и обслуживать вашу схему по требованию. Для маршрутизации `SchemaView` используйте помощник `get_schema_view()`. В `urls.py`: ```python from rest_framework.schemas import get_schema_view urlpatterns = [ # ... # Use the `get_schema_view()` helper to add a `SchemaView` to project URLs. # * `title` and `description` parameters are passed to `SchemaGenerator`. # * Provide view name for use with `reverse()`. path( "openapi", get_schema_view( title="Your Project", description="API for all things …", version="1.0.0" ), name="openapi-schema", ), # ... ] ``` #### `get_schema_view()`. Помощник `get_schema_view()` принимает следующие именованные аргументы: * `title`: Может использоваться для предоставления описательного заголовка для определения схемы. * `description`: Более длинный описательный текст. * `version`: Версия API. * `url`: Может использоваться для передачи канонического базового URL для схемы. ```python schema_view = get_schema_view( title='API мониторинга серверов', url='https://www.example.org/api/' ) ``` * `urlconf`: Строка, представляющая путь импорта к URL conf, для которого вы хотите сгенерировать схему API. По умолчанию это значение соответствует значению параметра Django `ROOT_URLCONF`. ```python schema_view = get_schema_view( title='API мониторинга серверов', url='https://www.example.org/api/', urlconf='myproject.urls' ) ``` * `patterns`: Список шаблонов url для ограничения интроспекции схемы. Если вам хотите, чтобы в схеме отображались только урлы `myproject.api`: ```python schema_url_patterns = [ path('api/', include('myproject.api.urls')), ] schema_view = get_schema_view( title='API мониторинга серверов', url='https://www.example.org/api/', patterns=schema_url_patterns, ) ``` * `public`: Может использоваться для указания того, должна ли схема обходить разрешения представления. По умолчанию False * `generator_class`: Может использоваться для указания подкласса `SchemaGenerator`, который будет передаваться в `SchemaView`. * `authentication_classes`: Может использоваться для указания списка классов аутентификации классов, которые будут применяться к конечной точке схемы. По умолчанию `settings.DEFAULT_AUTHENTICATION_CLASSES`. * `permission_classes`: Может использоваться для указания списка классов разрешений. которые будут применяться к конечной точке схемы. По умолчанию `settings.DEFAULT_PERMISSION_CLASSES`. * `renderer_classes`: Может использоваться для передачи набора классов рендереров, которые могут использоваться для рендеринга корневой конечной точки API. ## SchemaGenerator **Настройка на уровне схемы** ```python from rest_framework.schemas.openapi import SchemaGenerator ``` `SchemaGenerator` - это класс, который просматривает список шаблонов URL, запрашивает схему для каждого представления и собирает результирующую схему OpenAPI. Обычно вам не нужно самостоятельно создавать `SchemaGenerator`, но вы можете сделать это следующим образом: ```python generator = SchemaGenerator(title='Stock Prices API') ``` Аргументы: * `title` **обязательно**: Название API. * `description`: Более длинный описательный текст. * `version`: Версия API. По умолчанию `0.1.0`. * `url`: Корневой URL схемы API. Этот параметр не требуется, если схема не включена в префикс path. * `patterns`: Список URL-адресов для проверки при генерации схемы. По умолчанию используется URL conf проекта. * `urlconf`: Имя модуля URL conf для использования при генерации схемы. По умолчанию `settings.ROOT_URLCONF`. Чтобы настроить схему верхнего уровня, подкласс `rest_framework.schemas.openapi.SchemaGenerator` и предоставьте свой подкласс в качестве аргумента команде `generateschema` или вспомогательной функции `get_schema_view()`. ### get_schema(self, request=None, public=False) Возвращает словарь, представляющий схему OpenAPI: ```python generator = SchemaGenerator(title='Stock Prices API') schema = generator.get_schema() ``` Аргумент `request` является необязательным и может быть использован, если вы хотите применить разрешения для каждого пользователя к результирующей генерации схемы. Например, вы можете добавить условия обслуживания в [объект верхнего уровня `info`](https://swagger.io/specification/#infoObject): ```python class TOSSchemaGenerator(SchemaGenerator): def get_schema(self, *args, **kwargs): schema = super().get_schema(*args, **kwargs) schema["info"]["termsOfService"] = "https://example.com/tos.html" return schema ``` ## AutoSchema **Настройка для каждого представления** ```python from rest_framework.schemas.openapi import AutoSchema ``` По умолчанию интроспекция представления выполняется экземпляром `AutoSchema`, доступным через атрибут `schema` на `APIView`. ```python auto_schema = some_view.schema ``` `AutoSchema` предоставляет элементы OpenAPI, необходимые для каждого представления, метода запроса и пути: * Список [компонентов OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#componentsObject). В терминах DRF это отображения сериализаторов, которые описывают тела запроса и ответа. * Соответствующий [объект операции OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#operationObject), описывающий конечную точку, включая путь и параметры запроса для пагинации, фильтрации и так далее. ```python components = auto_schema.get_components(...) operation = auto_schema.get_operation(...) ``` При компиляции схемы `SchemaGenerator` вызывает `get_components()` и `get_operation()` для каждого представления, разрешенного метода и пути. --- **Примечание**: Автоматическая интроспекция компонентов и многих параметров операций опирается на соответствующие атрибуты и методы `GenericAPIView`: `get_serializer()`, `pagination_class`, `filter_backends` и т.д. По этой причине для базовых подклассов `APIView` интроспекция по умолчанию ограничивается именованными параметрами пути URL. --- `AutoSchema` инкапсулирует интроспекцию представления, необходимую для генерации схемы. Благодаря этому вся логика генерации схемы хранится в одном месте, а не распределяется по уже существующим API представления, сериализатора и полей. Следуя этому шаблону, старайтесь не допускать утечки логики схемы в ваши собственные представления, сериализаторы или поля при настройке генерации схемы. У вас может возникнуть соблазн сделать что-то вроде этого: ```python class CustomSchema(AutoSchema): """ AutoSchema subclass using schema_extra_info on the view. """ ... class CustomView(APIView): schema = CustomSchema() schema_extra_info = ... # some extra info ``` Здесь подкласс `AutoSchema` ищет `schema_extra_info` в представлении. Это *OK* (на самом деле это не вредит), но это означает, что в итоге вы получите логику схемы, разбросанную по разным местам. Вместо этого попробуйте подкласс `AutoSchema`, чтобы `extra_info` не просачивалась в представление: ```python class BaseSchema(AutoSchema): """ AutoSchema subclass that knows how to use extra_info. """ ... class CustomSchema(BaseSchema): extra_info = ... # some extra info class CustomView(APIView): schema = CustomSchema() ``` Этот стиль немного более многословен, но сохраняет инкапсуляцию кода, связанного со схемой. Он более *целостный* в *парламенте*. Это сделает остальной код вашего API более аккуратным. Если опция применяется ко многим классам представлений, вместо того, чтобы создавать отдельный подкласс для каждого представления, вам может показаться более удобным разрешить указывать опцию как `__init__()` именованный аргумент для вашего базового подкласса `AutoSchema`: ```python class CustomSchema(BaseSchema): def __init__(self, **kwargs): # store extra_info for later self.extra_info = kwargs.pop("extra_info") super().__init__(**kwargs) class CustomView(APIView): schema = CustomSchema(extra_info=...) # some extra info ``` Это избавит вас от необходимости создавать собственный подкласс для каждого вида для часто используемой опции. Не все методы `AutoSchema` раскрывают соответствующие ключи `__init__()`, но для наиболее часто используемых опций они есть. ### Методы `AutoSchema` #### `get_components()`. Генерирует компоненты OpenAPI, описывающие тела запросов и ответов, получая их свойства от сериализатора. Возвращает словарь, отображающий имя компонента на сгенерированное представление. По умолчанию он содержит только одну пару, но вы можете переопределить `get_components()`, чтобы вернуть несколько пар, если ваше представление использует несколько сериализаторов. #### `get_component_name()`. Вычисляет имя компонента из сериализатора. Вы можете увидеть предупреждения, если в вашем API есть дублирующиеся имена компонентов. В этом случае вы можете переопределить `get_component_name()` или передать `component_name` `__init__()` именованный аргумент (см. ниже), чтобы обеспечить разные имена. #### `get_reference()`. Возвращает ссылку на компонент сериализатора. Это может быть полезно, если вы переопределите `get_schema()`. #### `map_serializer()`. Сопоставляет сериализаторы с их представлениями OpenAPI. Большинство сериализаторов должны соответствовать стандартному типу OpenAPI `object`, но вы можете переопределить `map_serializer()`, чтобы настроить это или другие поля на уровне сериализатора. #### `map_field()`. Сопоставляет отдельные поля сериализатора с их схемным представлением. Базовая реализация будет работать с полями по умолчанию, которые предоставляет DRF. Для экземпляров `SerializerMethodField`, для которых схема неизвестна, или подклассов пользовательских полей следует переопределить `map_field()`, чтобы сгенерировать правильную схему: ```python class CustomSchema(AutoSchema): """Extension of ``AutoSchema`` to add support for custom field schemas.""" def map_field(self, field): # Handle SerializerMethodFields or custom fields here... # ... return super().map_field(field) ``` Авторы сторонних пакетов должны стремиться предоставить подкласс `AutoSchema` и миксин, переопределяющий `map_field()`, чтобы пользователи могли легко генерировать схемы для своих пользовательских полей. #### `get_tags()`. OpenAPI группирует операции по тегам. По умолчанию теги берутся из первого сегмента пути маршрутизируемого URL. Например, URL типа `/users/{id}/` будет генерировать тег `users`. Вы можете передать именованный аргумент в `__init__()`, чтобы вручную указать теги (см. ниже), или переопределить `get_tags()`, чтобы обеспечить пользовательскую логику. #### `get_operation()`. Возвращает [объект операции OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#operationObject), описывающий конечную точку, включая путь и параметры запроса для пагинации, фильтрации и так далее. Вместе с `get_components()` это основная точка входа в интроспекцию представления. #### `get_operation_id()`. Для каждой операции должен быть уникальный [`operationid`](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#fixed-fields-17). По умолчанию `operationId` выводится из имени модели, имени сериализатора или имени представления. `operationId` выглядит как "listItems", "retrieveItem", "updateItem" и т.д. По соглашению `operationId` используется camelCase. #### `get_operation_id_base()`. Если у вас есть несколько представлений с одинаковым именем модели, вы можете увидеть дублирующиеся идентификаторы операций. Чтобы обойти это, вы можете переопределить `get_operation_id_base()`, чтобы предоставить другую базу для именной части ID. #### `get_serializer()`. Если представление реализовало `get_serializer()`, возвращает результат. #### `get_request_serializer()`. По умолчанию возвращает `get_serializer()`, но может быть переопределен для различения объектов запроса и ответа. #### `get_response_serializer()`. По умолчанию возвращает `get_serializer()`, но может быть переопределен для различения объектов запроса и ответа. ### Именованные аргументы `AutoSchema.__init__()` `AutoSchema` предоставляет ряд именованных аргументов `__init__()`, которые могут быть использованы для общей настройки, если сгенерированные по умолчанию значения не подходят. Доступные именованные аргументы следующие: * `tags`: Укажите список тегов. * `component_name`: Укажите имя компонента. * `operation_id_base`: Укажите часть имени ресурса в идентификаторах операций. Вы передаете именованные аргументы при объявлении экземпляра `AutoSchema` в вашем представлении: ```python class PetDetailView(generics.RetrieveUpdateDestroyAPIView): schema = AutoSchema( tags=['Pets'], component_name='Pet', operation_id_base='Pet', ) ... ``` Предполагая модель `Pet` и сериализатор `PetSerializer`, именованные аргументы в этом примере, вероятно, не нужны. Однако, часто вам придется передавать именованные аргументы, если у вас есть несколько представлений, нацеленных на одну и ту же модель, или несколько представлений с одинаковыми именами сериализаторов. Если ваши представления имеют связанные настройки, которые часто требуются, вы можете создать базовый подкласс `AutoSchema` для вашего проекта, который принимает дополнительные `__init__()` именованные аргументы, чтобы избежать подклассификации `AutoSchema` для каждого представления. ================================================ FILE: api-guide/serializers.md ================================================ --- источник: - serializers.py --- # Сериализаторы > Расширение полезности сериализаторов - это то, чем мы хотели бы заняться. Однако это не тривиальная проблема, и она потребует серьезной работы над дизайном. > > — Russell Keith-Magee, [Django users group](https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion) Сериализаторы позволяют преобразовывать сложные данные, такие как наборы запросов и экземпляры моделей, в собственные типы данных Python, которые затем могут быть легко преобразованы в `JSON`, `XML` или другие типы содержимого. Сериализаторы также обеспечивают десериализацию, позволяя преобразовывать разобранные данные обратно в сложные типы после предварительной проверки входящих данных. Сериализаторы в DRF работают очень похоже на классы Django `Form` и `ModelForm`. Мы предоставляем класс `Serializer`, который дает вам мощный, универсальный способ управления выводом ваших ответов, а также класс `ModelSerializer`, который предоставляет полезный ярлык для создания сериализаторов, работающих с экземплярами моделей и наборами запросов. ## Объявление сериализаторов Давайте начнем с создания простого объекта, который мы можем использовать для примера: ```python from datetime import datetime class Comment: def __init__(self, email, content, created=None): self.email = email self.content = content self.created = created or datetime.now() comment = Comment(email='leila@example.com', content='foo bar') ``` Мы объявим сериализатор, который мы можем использовать для сериализации и десериализации данных, соответствующих объектам `Comment`. Объявление сериализатора очень похоже на объявление формы: ```python from rest_framework import serializers class CommentSerializer(serializers.Serializer): email = serializers.EmailField() content = serializers.CharField(max_length=200) created = serializers.DateTimeField() ``` ## Сериализация объектов Теперь мы можем использовать `CommentSerializer` для сериализации комментария или списка комментариев. Опять же, использование класса `Serializer` очень похоже на использование класса `Form`. ```python serializer = CommentSerializer(comment) serializer.data # {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'} ``` На данном этапе мы перевели экземпляр модели в собственные типы данных Python. Чтобы завершить процесс сериализации, мы преобразуем данные в `json`. ```python from rest_framework.renderers import JSONRenderer json = JSONRenderer().render(serializer.data) json # b'{"email":"leila@example.com","content":"foo bar","created":"2016-01-27T15:17:10.375877"}' ``` ## Десериализация объектов Десериализация аналогична. Сначала мы разбираем поток на собственные типы данных Python... ```python import io from rest_framework.parsers import JSONParser stream = io.BytesIO(json) data = JSONParser().parse(stream) ``` затем мы восстанавливаем эти родные типы данных в словарь проверенных данных. ```python serializer = CommentSerializer(data=data) serializer.is_valid() # True serializer.validated_data # {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)} ``` ## Сохранение экземпляров Если мы хотим иметь возможность возвращать полные экземпляры объектов на основе проверенных данных, нам необходимо реализовать один или оба метода `.create()` и `.update()`. Например: ```python class CommentSerializer(serializers.Serializer): email = serializers.EmailField() content = serializers.CharField(max_length=200) created = serializers.DateTimeField() def create(self, validated_data): return Comment(**validated_data) def update(self, instance, validated_data): instance.email = validated_data.get('email', instance.email) instance.content = validated_data.get('content', instance.content) instance.created = validated_data.get('created', instance.created) return instance ``` Если ваши экземпляры объектов соответствуют моделям Django, вы также захотите убедиться, что эти методы сохраняют объект в базе данных. Например, если `Comment` является моделью Django, методы могут выглядеть следующим образом: ```python def create(self, validated_data): return Comment.objects.create(**validated_data) def update(self, instance, validated_data): instance.email = validated_data.get('email', instance.email) instance.content = validated_data.get('content', instance.content) instance.created = validated_data.get('created', instance.created) instance.save() return instance ``` Теперь при десериализации данных мы можем вызвать `.save()`, чтобы вернуть экземпляр объекта, основанный на проверенных данных. ```python comment = serializer.save() ``` Вызов `.save()` либо создаст новый экземпляр, либо обновит существующий, в зависимости от того, был ли передан существующий экземпляр при инстанцировании класса сериализатора: ```python # .save() will create a new instance. serializer = CommentSerializer(data=data) # .save() will update the existing `comment` instance. serializer = CommentSerializer(comment, data=data) ``` Методы `.create()` и `.update()` являются необязательными. Вы можете реализовать либо ни один из них, либо один, либо оба, в зависимости от условий использования вашего класса сериализатора. #### Передача дополнительных атрибутов в `.save()`. Иногда вы хотите, чтобы код представления мог вводить дополнительные данные в момент сохранения экземпляра. Эти дополнительные данные могут включать информацию о текущем пользователе, текущем времени или что-нибудь еще, что не является частью данных запроса. Вы можете сделать это, включив дополнительные именованные аргументы при вызове `.save()`. Например: ```python serializer.save(owner=request.user) ``` Любые дополнительные именованные аргументы будут включены в аргумент `validated_data` при вызове `.create()` или `.update()`. #### Переопределение `.save()` напрямую. В некоторых случаях имена методов `.create()` и `.update()` могут не иметь смысла. Например, в контактной форме мы можем не создавать новые экземпляры, а отправлять электронное письмо или другое сообщение. В этих случаях вы можете вместо этого переопределить `.save()` напрямую, как более читабельный и осмысленный. Например: ```python class ContactForm(serializers.Serializer): email = serializers.EmailField() message = serializers.CharField() def save(self): email = self.validated_data['email'] message = self.validated_data['message'] send_email(from=email, message=message) ``` Обратите внимание, что в приведенном выше случае нам приходится напрямую обращаться к свойству сериализатора `.validated_data`. ## Валидация При десериализации данных всегда нужно вызывать `is_valid()` прежде, чем пытаться получить доступ к проверенным данным или сохранить экземпляр объекта. Если возникнут ошибки валидации, свойство `.errors` будет содержать словарь, представляющий сообщения об ошибках. Например: ```python serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'}) serializer.is_valid() # False serializer.errors # {'email': ['Enter a valid email address.'], 'created': ['This field is required.']} ``` Каждый ключ в словаре будет именем поля, а значения будут списками строк любых сообщений об ошибках, соответствующих этому полю. Также может присутствовать ключ `non_field_errors`, в котором будут перечислены все общие ошибки валидации. Имя ключа `non_field_errors` можно настроить с помощью параметра DRF `NON_FIELD_ERRORS_KEY`. При десериализации списка элементов ошибки будут возвращены в виде списка словарей, представляющих каждый из десериализованных элементов. #### Возбуждение исключения при недопустимых данных Метод `.is_valid()` принимает необязательный флаг `raise_exception`, который заставит его поднять исключение `serializers.ValidationError`, если есть ошибки валидации. Эти исключения автоматически обрабатываются обработчиком исключений по умолчанию, который предоставляет DRF, и по умолчанию будут возвращать ответы `HTTP 400 Bad Request`. ```python # Return a 400 response if the data was invalid. serializer.is_valid(raise_exception=True) ``` #### Валидация на уровне поля Вы можете задать пользовательскую валидацию на уровне полей, добавив методы `.validate_<имя_поля>` в подкласс `Serializer`. Они аналогичны методам `.clean_<имя_поля>` в формах Django. Эти методы принимают единственный аргумент, который является значением поля, требующего проверки. Ваши методы `validate_<имя_поля>` должны возвращать проверенное значение или вызывать `serializers.ValidationError`. Например: ```python from rest_framework import serializers class BlogPostSerializer(serializers.Serializer): title = serializers.CharField(max_length=100) content = serializers.CharField() def validate_title(self, value): """ Check that the blog post is about Django. """ if 'django' not in value.lower(): raise serializers.ValidationError("Blog post is not about Django") return value ``` --- **Примечание:** Если ваше `<имя_поля>` объявлено в вашем сериализаторе с параметром `required=False`, то этот шаг валидации не будет выполняться, если поле не включено. --- #### Валидация на уровне объекта Чтобы выполнить любую другую проверку, требующую доступа к нескольким полям, добавьте метод `.validate()` к вашему подклассу `Serializer`. Этот метод принимает единственный аргумент, который является словарем значений полей. При необходимости он должен вызывать `serializers.ValidationError, или просто возвращать проверенные значения. Например: ```python from rest_framework import serializers class EventSerializer(serializers.Serializer): description = serializers.CharField(max_length=100) start = serializers.DateTimeField() finish = serializers.DateTimeField() def validate(self, data): """ Check that start is before finish. """ if data['start'] > data['finish']: raise serializers.ValidationError("finish must occur after start") return data ``` #### Валидаторы Отдельные поля сериализатора могут включать валидаторы, например, путем объявления их в экземпляре поля: ```python def multiple_of_ten(value): if value % 10 != 0: raise serializers.ValidationError('Not a multiple of ten') class GameRecord(serializers.Serializer): score = serializers.IntegerField(validators=[multiple_of_ten]) ... ``` Классы сериализаторов могут также включать многократно используемые валидаторы, которые применяются к полному набору данных поля. Эти валидаторы включаются путем объявления их во внутреннем классе `Meta`, например, так: ```python class EventSerializer(serializers.Serializer): name = serializers.CharField() room_number = serializers.ChoiceField(choices=[101, 102, 103, 201]) date = serializers.DateField() class Meta: # Each room only has one event per day. validators = [ UniqueTogetherValidator( queryset=Event.objects.all(), fields=['room_number', 'date'] ) ] ``` Для получения дополнительной информации см. документацию [validators](validators.md). ## Доступ к исходным данным и экземпляру При передаче исходного объекта или набора запросов экземпляру сериализатора, объект будет доступен как `.instance`. Если начальный объект не передан, то атрибут `.instance` будет иметь значение `None`. При передаче данных экземпляру сериализатора, немодифицированные данные будут доступны как `.initial_data`. Если именованный аргумент `data` не передан, то атрибут `.initial_data` не будет существовать. ## Частичные обновления По умолчанию сериализаторам должны передаваться значения для всех обязательных полей, иначе они будут выдавать ошибки валидации. Вы можете использовать аргумент `partial`, чтобы разрешить частичное обновление. ```python # Update `comment` with partial data serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True) ``` ## Работа с вложенными объектами Предыдущие примеры хорошо подходят для работы с объектами, которые имеют только простые типы данных, но иногда нам также необходимо иметь возможность представлять более сложные объекты, где некоторые атрибуты объекта могут не быть простыми типами данных, такими как строки, даты или целые числа. Класс `Serializer` сам является типом `Field` и может быть использован для представления отношений, в которых один тип объекта вложен в другой. ```python class UserSerializer(serializers.Serializer): email = serializers.EmailField() username = serializers.CharField(max_length=100) class CommentSerializer(serializers.Serializer): user = UserSerializer() content = serializers.CharField(max_length=200) created = serializers.DateTimeField() ``` Если вложенное представление может опционально принимать значение `None`, вы должны передать флаг `required=False` вложенному сериализатору. ```python class CommentSerializer(serializers.Serializer): user = UserSerializer(required=False) # May be an anonymous user. content = serializers.CharField(max_length=200) created = serializers.DateTimeField() ``` Аналогично, если вложенное представление должно быть списком элементов, вы должны передать флаг `many=True` в сериализатор вложенных элементов. ```python class CommentSerializer(serializers.Serializer): user = UserSerializer(required=False) edits = EditItemSerializer(many=True) # A nested list of 'edit' items. content = serializers.CharField(max_length=200) created = serializers.DateTimeField() ``` ## Записываемые вложенные представления При работе с вложенными представлениями, поддерживающими десериализацию данных, любые ошибки с вложенными объектами будут вложены под именем поля вложенного объекта. ```python serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'}) serializer.is_valid() # False serializer.errors # {'user': {'email': ['Enter a valid email address.']}, 'created': ['This field is required.']} ``` Аналогично, свойство `.validated_data` будет включать в себя вложенные структуры данных. #### Написание методов `.create()` для вложенных представлений Если вы поддерживаете записываемые вложенные представления, вам нужно написать методы `.create()` или `.update()`, которые обрабатывают сохранение нескольких объектов. В следующем примере показано, как можно создать пользователя с вложенным объектом профиля. ```python class UserSerializer(serializers.ModelSerializer): profile = ProfileSerializer() class Meta: model = User fields = ['username', 'email', 'profile'] def create(self, validated_data): profile_data = validated_data.pop('profile') user = User.objects.create(**validated_data) Profile.objects.create(user=user, **profile_data) return user ``` #### Написание методов `.update()` для вложенных представлений Для обновлений вам необходимо тщательно продумать, как обрабатывать обновления отношений. Например, если данные для отношения `None`, или не предоставлены, что из перечисленного ниже должно произойти? * Установите для отношения значение `NULL` в базе данных. * Удалите связанный экземпляр. * Игнорировать данные и оставить экземпляр как есть. * Вызвать ошибку валидации. Вот пример метода `.update()` для нашего предыдущего класса `UserSerializer`. ```python def update(self, instance, validated_data): profile_data = validated_data.pop('profile') # Unless the application properly enforces that this field is # always set, the following could raise a `DoesNotExist`, which # would need to be handled. profile = instance.profile instance.username = validated_data.get('username', instance.username) instance.email = validated_data.get('email', instance.email) instance.save() profile.is_premium_member = profile_data.get( 'is_premium_member', profile.is_premium_member ) profile.has_support_contract = profile_data.get( 'has_support_contract', profile.has_support_contract ) profile.save() return instance ``` Поскольку поведение вложенных созданий и обновлений может быть неоднозначным и может требовать сложных зависимостей между связанными моделями, DRF 3 требует, чтобы вы всегда писали эти методы явно. Методы `ModelSerializer` `.create()` и `.update()` по умолчанию не включают поддержку записываемых вложенных представлений. Однако существуют сторонние пакеты, такие как [DRF Writable Nested](serializers.md#drf-writable-nested), которые поддерживают автоматические записываемые вложенные представления. #### Обработка сохранения связанных экземпляров в классах менеджера моделей Альтернативой сохранению нескольких связанных экземпляров в сериализаторе является написание пользовательских классов менеджера модели, которые занимаются созданием нужных экземпляров. Например, предположим, мы хотим, чтобы экземпляры `User` и `Profile` всегда создавались вместе как пара. Мы можем написать пользовательский класс менеджера, который будет выглядеть примерно так: ```python class UserManager(models.Manager): ... def create(self, username, email, is_premium_member=False, has_support_contract=False): user = User(username=username, email=email) user.save() profile = Profile( user=user, is_premium_member=is_premium_member, has_support_contract=has_support_contract ) profile.save() return user ``` Этот класс менеджера теперь более точно передает, что экземпляры пользователя и профиля всегда создаются одновременно. Наш метод `.create()` в классе сериализатора теперь может быть переписан для использования нового метода менеджера. ```python def create(self, validated_data): return User.objects.create( username=validated_data['username'], email=validated_data['email'], is_premium_member=validated_data['profile']['is_premium_member'], has_support_contract=validated_data['profile']['has_support_contract'] ) ``` Более подробно об этом подходе смотрите документацию Django по [менеджерам моделей](https://docs.djangoproject.com/en/stable/topics/db/managers/), и [этот блогпост об использовании классов моделей и менеджеров](https://www.dabapps.com/blog/django-models-and-encapsulation/). ## Работа с несколькими объектами Класс `Serializer` также может обрабатывать сериализацию или десериализацию списков объектов. #### Сериализация нескольких объектов Чтобы сериализовать кверисет или список объектов вместо одного экземпляра объекта, необходимо передать флаг `many=True` при инстанцировании сериализатора. Затем вы можете передать кверисет или список объектов для сериализации. ```python queryset = Book.objects.all() serializer = BookSerializer(queryset, many=True) serializer.data # [ # {'id': 0, 'title': 'The electric kool-aid acid test', 'author': 'Tom Wolfe'}, # {'id': 1, 'title': 'If this is a man', 'author': 'Primo Levi'}, # {'id': 2, 'title': 'The wind-up bird chronicle', 'author': 'Haruki Murakami'} # ] ``` #### Десериализация нескольких объектов Поведение по умолчанию для десериализации нескольких объектов - это поддержка создания нескольких объектов, но не поддержка обновления нескольких объектов. Для получения дополнительной информации о том, как поддержать или настроить любой из этих случаев, см. документацию по [ListSerializer](#listserializer) ниже. ## Включение дополнительного контекста Бывают случаи, когда вам необходимо предоставить сериализатору дополнительный контекст в дополнение к сериализуемому объекту. Одним из распространенных случаев является использование сериализатора, который включает отношения с гиперссылками, что требует, чтобы сериализатор имел доступ к текущему запросу, чтобы он мог правильно генерировать полностью определенные URL. Вы можете предоставить произвольный дополнительный контекст, передав аргумент `context` при инстанцировании сериализатора. Например: ```python serializer = AccountSerializer(account, context={'request': request}) serializer.data # {'id': 6, 'owner': 'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'} ``` Контекстный словарь можно использовать в любой логике поля сериализатора, например, в пользовательском методе `.to_representation()`, обращаясь к атрибуту `self.context`. --- # ModelSerializer Часто вам понадобятся классы сериализаторов, которые близко сопоставляются с определениями моделей Django. Класс `ModelSerializer` предоставляет ярлык, позволяющий автоматически создать класс `Serializer` с полями, соответствующими полям модели. **Класс `ModelSerializer` такой же, как и обычный класс `Serializer`, за исключением того, что**: * Он автоматически сгенерирует для вас набор полей на основе модели. * Он автоматически генерирует валидаторы для сериализатора, такие как валидаторы unique_together. * Он включает простые реализации по умолчанию `.create()` и `.update()`. Объявление `ModelSerializer` выглядит следующим образом: ```python class AccountSerializer(serializers.ModelSerializer): class Meta: model = Account fields = ['id', 'account_name', 'users', 'created'] ``` По умолчанию все поля модели класса будут отображены на соответствующие поля сериализатора. Любые отношения, такие как внешние ключи в модели, будут отображены на `PrimaryKeyRelatedField`. Обратные отношения не включаются по умолчанию, если они не включены явно, как указано в документации [serializer relations](relations.md). #### Проверка `ModelSerializer`. Классы сериализаторов генерируют полезные строки представления, которые позволяют полностью просмотреть состояние их полей. Это особенно полезно при работе с `ModelSerializers`, когда вы хотите определить, какой набор полей и валидаторов автоматически создается для вас. Для этого откройте оболочку Django, используя `python manage.py shell`, затем импортируйте класс сериализатора, инстанцируйте его и выведите представление объекта... ```python >>> from myapp.serializers import AccountSerializer >>> serializer = AccountSerializer() >>> print(repr(serializer)) AccountSerializer(): id = IntegerField(label='ID', read_only=True) name = CharField(allow_blank=True, max_length=100, required=False) owner = PrimaryKeyRelatedField(queryset=User.objects.all()) ``` ## Указание, какие поля включать Если вы хотите, чтобы в сериализаторе модели использовалось только подмножество полей по умолчанию, вы можете сделать это с помощью опций `fields` или `exclude`, как и в случае с `ModelForm`. Настоятельно рекомендуется явно задавать все поля, которые должны быть сериализованы, с помощью атрибута `fields`. Это уменьшит вероятность непреднамеренного раскрытия данных при изменении ваших моделей. Например: ```python class AccountSerializer(serializers.ModelSerializer): class Meta: model = Account fields = ['id', 'account_name', 'users', 'created'] ``` Вы также можете установить для атрибута `fields` специальное значение `'__all__'`, чтобы указать, что должны использоваться все поля в модели. Например: ```python class AccountSerializer(serializers.ModelSerializer): class Meta: model = Account fields = '__all__' ``` Вы можете установить атрибут `exclude` в список полей, которые должны быть исключены из сериализатора. Например: ```python class AccountSerializer(serializers.ModelSerializer): class Meta: model = Account exclude = ['users'] ``` В приведенном выше примере, если модель `Account` имеет 3 поля `account_name`, `users` и `created`, это приведет к тому, что поля `account_name` и `created` будут сериализованы. Имена в атрибутах `fields` и `exclude` обычно отображаются на поля модели в классе модели. Альтернативные имена в опциях `fields` могут отображаться на свойства или методы, не принимающие аргументов, которые существуют в классе модели. Начиная с версии 3.3.0, **обязательным** является предоставление одного из атрибутов `fields` или `exclude`. ## Указание вложенной сериализации По умолчанию `ModelSerializer` использует первичные ключи для отношений, но вы также можете легко генерировать вложенные представления с помощью опции `depth`: ```python class AccountSerializer(serializers.ModelSerializer): class Meta: model = Account fields = ['id', 'account_name', 'users', 'created'] depth = 1 ``` Параметр `depth` должен быть установлен в целочисленное значение, которое указывает глубину отношений, которые должны быть пройдены перед возвратом к плоскому представлению. Если вы хотите настроить способ сериализации, вам нужно будет определить поле самостоятельно. ## Указание полей в явном виде Вы можете добавить дополнительные поля в `ModelSerializer` или переопределить поля по умолчанию, объявив поля в классе, как и в классе `Serializer`. ```python class AccountSerializer(serializers.ModelSerializer): url = serializers.CharField(source='get_absolute_url', read_only=True) groups = serializers.PrimaryKeyRelatedField(many=True) class Meta: model = Account fields = ['url', 'groups'] ``` Дополнительные поля могут соответствовать любому свойству или вызываемому объекту модели. ## Указание полей, доступных только для чтения Вы можете указать несколько полей как доступные только для чтения. Вместо того чтобы добавлять каждое поле явно с атрибутом `read_only=True`, вы можете использовать сокращенную опцию Meta, `read_only_fields`. Этот параметр должен представлять собой список или кортеж имен полей и объявляется следующим образом: ```python class AccountSerializer(serializers.ModelSerializer): class Meta: model = Account fields = ['id', 'account_name', 'users', 'created'] read_only_fields = ['account_name'] ``` Поля модели, для которых установлено значение `editable=False`, и поля `AutoField` по умолчанию будут установлены в режим только для чтения, и их не нужно добавлять в опцию `read_only_fields`. --- **Примечание**: Существует особый случай, когда поле, доступное только для чтения, является частью ограничения `unique_together` на уровне модели. В этом случае поле требуется классу сериализатора для проверки ограничения, но оно также не должно быть доступно для редактирования пользователем. Правильный способ решения этой проблемы — явно указать поле в сериализаторе, предоставив аргументы `read_only=True` и `default=…`. Одним из примеров этого является отношение только для чтения к текущему аутентифицированному `User`, который является `unique_together` с другим идентификатором. В этом случае вы объявите поле пользователя следующим образом: ```python user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault()) ``` Пожалуйста, ознакомьтесь с документацией [Validators Documentation](validators.md) для получения подробной информации о классах [UniqueTogetherValidator](validators.md#uniquetogethervalidator) и [CurrentUserDefault](validators.md#currentuserdefault). --- ## Дополнительные именованные аргументы Также есть возможность указать произвольные дополнительные именованные аргументы для полей, используя опцию `extra_kwargs`. Как и в случае с `read_only_fields`, это означает, что вам не нужно явно объявлять поле в сериализаторе. Эта опция представляет собой словарь, отображающий имена полей на словарь именованных аргументов. Например: ```python class CreateUserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ['email', 'username', 'password'] extra_kwargs = {'password': {'write_only': True}} def create(self, validated_data): user = User( email=validated_data['email'], username=validated_data['username'] ) user.set_password(validated_data['password']) user.save() return user ``` Следует помнить, что если поле уже было явно объявлено в классе сериализатора, то опция `extra_kwargs` будет проигнорирована. ## Реляционные поля При сериализации экземпляров модели существует несколько различных способов представления отношений. Представление по умолчанию для `ModelSerializer` заключается в использовании первичных ключей связанных экземпляров. Альтернативные представления включают сериализацию с помощью гиперссылок, сериализацию полных вложенных представлений или сериализацию с помощью пользовательского представления. Более подробную информацию можно найти в документации [serializer relations](relations.md). ## Настройка сопоставлений полей Класс ModelSerializer также предоставляет API, который вы можете переопределить, чтобы изменить способ автоматического определения полей сериализатора при инстанцировании сериализатора. Обычно, если `ModelSerializer` не генерирует нужные вам поля по умолчанию, вы должны либо добавить их в класс явно, либо просто использовать вместо них обычный класс `Serializer`. Однако в некоторых случаях вы можете захотеть создать новый базовый класс, определяющий, как создаются поля сериализатора для любой конкретной модели. ### `serializer_field_mapping`. Отображение полей модели Django на поля сериализатора DRF. Вы можете переопределить это отображение, чтобы изменить поля сериализатора по умолчанию, которые должны использоваться для каждого поля модели. ### `serializer_related_field`. Это свойство должно быть классом поля сериализатора, который по умолчанию используется для реляционных полей. Для `ModelSerializer` это значение по умолчанию равно `serializers.PrimaryKeyRelatedField`. Для `HyperlinkedModelSerializer` это значение по умолчанию равно `serializers.HyperlinkedRelatedField`. ### `serializer_url_field`. Класс поля сериализатора, который должен использоваться для любого поля `url` в сериализаторе. По умолчанию `serializers.HyperlinkedIdentityField`. ### `serializer_choice_field` Класс поля сериализатора, который должен использоваться для любых полей выбора в сериализаторе. По умолчанию `serializers.ChoiceField`. ### API field_class и field_kwargs Следующие методы вызываются для определения класса и именованных аргументов для каждого поля, которое должно быть автоматически включено в сериализатор. Каждый из этих методов должен возвращать кортеж `(field_class, field_kwargs)`. ### `build_standard_field(self, field_name, model_field)`. Вызывается для генерации поля сериализатора, которое сопоставляется со стандартным полем модели. Реализация по умолчанию возвращает класс сериализатора на основе атрибута `serializer_field_mapping`. ### `build_relational_field(self, field_name, relation_info)`. Вызывается для генерации поля сериализатора, которое сопоставляется с полем реляционной модели. Реализация по умолчанию возвращает класс сериализатора на основе атрибута `serializer_related_field`. Аргумент `relation_info` представляет собой именованный кортеж, содержащий свойства `model_field`, `related_model`, `to_many` и `has_through_model`. ### `build_nested_field(self, field_name, relation_info, nested_depth)`. Вызывается для генерации поля сериализатора, которое сопоставляется с полем реляционной модели, если установлен параметр `depth`. Реализация по умолчанию динамически создает вложенный класс сериализатора на основе `ModelSerializer` или `HyperlinkedModelSerializer`. Значение `nested_depth` будет равно значению опции `depth`, минус один. Аргумент `relation_info` представляет собой именованный кортеж, содержащий свойства `model_field`, `related_model`, `to_many` и `has_through_model`. ### `build_property_field(self, field_name, model_class)`. Вызывается для генерации поля сериализатора, которое сопоставляется со свойством или методом с нулевым аргументом класса модели. Реализация по умолчанию возвращает класс `ReadOnlyField`. ### `build_url_field(self, field_name, model_class)`. Вызывается для генерации поля сериализатора для собственного поля сериализатора `url`. Реализация по умолчанию возвращает класс `HyperlinkedIdentityField`. ### `build_unknown_field(self, field_name, model_class)`. Вызывается, когда имя поля не сопоставлено ни с одним полем модели или свойством модели. Реализация по умолчанию вызывает ошибку, хотя подклассы могут настраивать это поведение. --- # HyperlinkedModelSerializer Класс `HyperlinkedModelSerializer` похож на класс `ModelSerializer`, за исключением того, что он использует гиперссылки для представления отношений, а не первичные ключи. По умолчанию сериализатор будет включать поле `url` вместо поля первичного ключа. Поле url будет представлено с помощью поля сериализатора `HyperlinkedIdentityField`, а любые отношения в модели будут представлены с помощью поля сериализатора `HyperlinkedRelatedField`. Вы можете явно включить первичный ключ, добавив его, например, в опцию `fields`: ```python class AccountSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Account fields = ['url', 'id', 'account_name', 'users', 'created'] ``` ## Абсолютные и относительные URL-адреса При инстанцировании `HyperlinkedModelSerializer` вы должны включить текущий `request` в контекст сериализатора, например: ```python serializer = AccountSerializer(queryset, context={'request': request}) ``` Это гарантирует, что гиперссылки будут содержать соответствующее имя хоста, чтобы в результирующем представлении использовались полные URL-адреса, например: ```python http://api.example.com/accounts/1/ ``` Вместо относительных URL-адресов, таких как: ```python /accounts/1/ ``` Если вы *хотите* использовать относительные URL, вы должны явно передать `{'request': None}` в контексте сериализатора. ## Как определяются представления с гиперссылками Необходимо определить, какие представления следует использовать для гиперссылок на экземпляры модели. По умолчанию ожидается, что гиперссылки будут соответствовать имени представления, которое соответствует стилю `'{имя_модели}-detail'`, и ищет экземпляр по именованному аргументу `pk`. Вы можете переопределить имя представления поля URL и поле поиска, используя один или оба параметра `view_name` и `lookup_field` в параметре `extra_kwargs`, как показано ниже: ```python class AccountSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Account fields = ['url', 'account_name', 'users', 'created'] extra_kwargs = { 'url': {'view_name': 'accounts', 'lookup_field': 'account_name'}, 'users': {'lookup_field': 'username'} } ``` В качестве альтернативы вы можете явно задать поля в сериализаторе. Например: ```python class AccountSerializer(serializers.HyperlinkedModelSerializer): url = serializers.HyperlinkedIdentityField( view_name='accounts', lookup_field='slug' ) users = serializers.HyperlinkedRelatedField( view_name='user-detail', lookup_field='username', many=True, read_only=True ) class Meta: model = Account fields = ['url', 'account_name', 'users', 'created'] ``` --- **Совет**: Правильное согласование гиперссылочных представлений и вашего URL conf иногда может быть немного сложным. Печать `repr` экземпляра `HyperlinkedModelSerializer` - особенно полезный способ проверить, какие именно имена представлений и поля поиска должны отображать отношения. --- ## Изменение имени поля URL Имя поля URL по умолчанию равно 'url'. Вы можете переопределить его глобально, используя параметр `URL_FIELD_NAME`. --- # ListSerializer Класс `ListSerializer` обеспечивает поведение для сериализации и валидации нескольких объектов одновременно. Обычно вам не нужно использовать `ListSerializer` напрямую, а следует просто передать `many=True` при инстанцировании сериализатора. При инстанцировании сериализатора и передаче `many=True` будет создан экземпляр `ListSerializer`. Затем класс сериализатора становится дочерним классом родительского `ListSerializer`. Следующий аргумент также может быть передан полю `ListSerializer` или сериализатору, которому передано `many=True`: ### `allow_empty` По умолчанию это `True`, но может быть установлено в `False`, если вы хотите запретить пустые списки в качестве допустимого ввода. ### `max_length` По умолчанию это `None`, но может быть установлено в положительное целое число, если вы хотите проверить, что список содержит не более этого количества элементов. ### `min_length` По умолчанию это `None`, но может быть установлено в положительное целое число, если вы хотите проверить, что список содержит не менее этого количества элементов. ### Настройка поведения `ListSerializer`. *Существует* несколько случаев, когда вы захотите настроить поведение `ListSerializer`. Например: * Вы хотите обеспечить определенную проверку списков, например, проверить, что один элемент не конфликтует с другим элементом списка. * Вы хотите настроить поведение создания или обновления нескольких объектов. Для этих случаев вы можете изменить класс, который используется при передаче `many=True`, используя опцию `list_serializer_class` для класса сериализатора `Meta`. Например: ```python class CustomListSerializer(serializers.ListSerializer): ... class CustomSerializer(serializers.Serializer): ... class Meta: list_serializer_class = CustomListSerializer ``` #### Настройка множественного создания Реализация по умолчанию для создания нескольких объектов заключается в простом вызове `.create()` для каждого элемента списка. Если вы хотите настроить это поведение, вам нужно настроить метод `.create()` класса `ListSerializer`, который используется, когда передается `many=True`. Например: ```python class BookListSerializer(serializers.ListSerializer): def create(self, validated_data): books = [Book(**item) for item in validated_data] return Book.objects.bulk_create(books) class BookSerializer(serializers.Serializer): ... class Meta: list_serializer_class = BookListSerializer ``` #### Настройка многократного обновления По умолчанию класс `ListSerializer` не поддерживает множественные обновления. Это связано с тем, что поведение, которое следует ожидать для вставок и удалений, неоднозначно. Для поддержки нескольких обновлений необходимо сделать это явно. При написании кода множественных обновлений обязательно учитывайте следующее: * Как определить, какой экземпляр должен быть обновлен для каждого элемента в списке данных? * Как следует обрабатывать вставки? Являются ли они недействительными, или они создают новые объекты? * Как следует обрабатывать удаления? Означают ли они удаление объекта или удаление отношения? Следует ли их молча игнорировать, или они недействительны? * Как следует обрабатывать упорядочивание? Влечет ли изменение положения двух объектов изменение состояния или оно игнорируется? Вам нужно будет добавить явное поле `id` в сериализатор экземпляра. По умолчанию неявно генерируемое поле `id` помечено как `read_only`. Это приводит к тому, что оно удаляется при обновлении. Как только вы объявите его явно, оно будет доступно в методе `update` сериализатора списка. Вот пример того, как можно реализовать несколько обновлений: ```python class BookListSerializer(serializers.ListSerializer): def update(self, instance, validated_data): # Maps for id->instance and id->data item. book_mapping = {book.id: book for book in instance} data_mapping = {item['id']: item for item in validated_data} # Perform creations and updates. ret = [] for book_id, data in data_mapping.items(): book = book_mapping.get(book_id, None) if book is None: ret.append(self.child.create(data)) else: ret.append(self.child.update(book, data)) # Perform deletions. for book_id, book in book_mapping.items(): if book_id not in data_mapping: book.delete() return ret class BookSerializer(serializers.Serializer): # We need to identify elements in the list using their primary key, # so use a writable field here, rather than the default which would be read-only. id = serializers.IntegerField() ... class Meta: list_serializer_class = BookListSerializer ``` #### Настройка инициализации ListSerializer Когда инстанцируется сериализатор с `many=True`, нам необходимо определить, какие аргументы и ключевые слова следует передать в метод `.__init__()` как для дочернего класса `Serializer`, так и для родительского класса `ListSerializer`. По умолчанию все аргументы передаются обоим классам, за исключением `validators` и любых пользовательских именованных аргументов, которые, как предполагается, предназначены для дочернего класса сериализатора. Иногда вам может понадобиться явно указать, как дочерний и родительский классы должны быть инстанцированы при передаче `many=True`. Вы можете сделать это с помощью метода класса `many_init`. ```python @classmethod def many_init(cls, *args, **kwargs): # Instantiate the child serializer. kwargs['child'] = cls() # Instantiate the parent list serializer. return CustomListSerializer(*args, **kwargs) ``` --- # BaseSerializer Класс `BaseSerializer`, который можно использовать для простой поддержки альтернативных стилей сериализации и десериализации. Этот класс реализует тот же базовый API, что и класс `Serializer`: * `.data` - Возвращает исходящее примитивное представление. * `.is_valid()` - Десериализует и проверяет входящие данные. * `.validated_data` - Возвращает проверенные входящие данные. * `.errors` - Возвращает любые ошибки во время валидации. * `.save()` - Сохраняет проверенные данные в экземпляре объекта. Есть четыре метода, которые могут быть переопределены, в зависимости от того, какую функциональность вы хотите, чтобы поддерживал класс сериализатора: * `.to_representation()` - Переопределить это для поддержки сериализации, для операций чтения. * `.to_internal_value()` - Переопределить это для поддержки десериализации, для операций записи. * `.create()` и `.update()` - Переопределите один из этих параметров или оба для поддержки сохранения экземпляров. Поскольку этот класс предоставляет тот же интерфейс, что и класс `Serializer`, вы можете использовать его с существующими общими представлениями на основе классов точно так же, как и обычный `Serializer` или `ModelSerializer`. Единственное отличие, которое вы заметите при этом - классы `BaseSerializer` не будут генерировать HTML-формы в Web-интерфейсе API. Это происходит потому, что данные, которые они возвращают, не включают всю информацию о полях, которая позволит преобразовать каждое поле в подходящий HTML-ввод. #### Классы `BaseSerializer` только для чтения. Чтобы реализовать сериализатор только для чтения, используя класс `BaseSerializer`, нам просто нужно переопределить метод `.to_representation()`. Давайте рассмотрим пример на примере простой модели Django: ```python class HighScore(models.Model): created = models.DateTimeField(auto_now_add=True) player_name = models.CharField(max_length=10) score = models.IntegerField() ``` Очень просто создать сериализатор только для чтения для преобразования экземпляров `HighScore` в примитивные типы данных. ```python class HighScoreSerializer(serializers.BaseSerializer): def to_representation(self, instance): return { 'score': instance.score, 'player_name': instance.player_name } ``` Теперь мы можем использовать этот класс для сериализации отдельных экземпляров `HighScore`: ```python @api_view(['GET']) def high_score(request, pk): instance = HighScore.objects.get(pk=pk) serializer = HighScoreSerializer(instance) return Response(serializer.data) ``` Или используйте его для сериализации нескольких экземпляров: ```python @api_view(['GET']) def all_high_scores(request): queryset = HighScore.objects.order_by('-score') serializer = HighScoreSerializer(queryset, many=True) return Response(serializer.data) ``` #### Классы `BaseSerializer` с функцией чтения-записи Для создания сериализатора чтения-записи нам сначала нужно реализовать метод `.to_internal_value()`. Этот метод возвращает проверенные значения, которые будут использованы для создания экземпляра объекта, и может вызвать `serializers.ValidationError`, если предоставленные данные имеют неправильный формат. Как только вы реализуете `.to_internal_value()`, базовый API валидации будет доступен в сериализаторе, и вы сможете использовать `.is_valid()`, `.validated_data` и `.errors`. Если вы хотите также поддерживать `.save()`, вам необходимо также реализовать один или оба метода `.create()` и `.update()`. Вот полный пример нашего предыдущего `HighScoreSerializer`, который был обновлен для поддержки операций чтения и записи. ```python class HighScoreSerializer(serializers.BaseSerializer): def to_internal_value(self, data): score = data.get('score') player_name = data.get('player_name') # Perform the data validation. if not score: raise serializers.ValidationError({ 'score': 'This field is required.' }) if not player_name: raise serializers.ValidationError({ 'player_name': 'This field is required.' }) if len(player_name) > 10: raise serializers.ValidationError({ 'player_name': 'May not be more than 10 characters.' }) # Return the validated values. This will be available as # the `.validated_data` property. return { 'score': int(score), 'player_name': player_name } def to_representation(self, instance): return { 'score': instance.score, 'player_name': instance.player_name } def create(self, validated_data): return HighScore.objects.create(**validated_data) ``` #### Создание новых базовых классов Класс `BaseSerializer` также полезен, если вы хотите реализовать новые общие классы сериализаторов для работы с определенными стилями сериализации или для интеграции с альтернативными бэкендами хранения данных. Следующий класс является примером общего сериализатора, который может обрабатывать принудительное преобразование произвольных сложных объектов в примитивные представления. ```python class ObjectSerializer(serializers.BaseSerializer): """ A read-only serializer that coerces arbitrary complex objects into primitive representations. """ def to_representation(self, instance): output = {} for attribute_name in dir(instance): attribute = getattr(instance, attribute_name) if attribute_name.startswith('_'): # Ignore private attributes. pass elif hasattr(attribute, '__call__'): # Ignore methods and other callables. pass elif isinstance(attribute, (str, int, bool, float, type(None))): # Primitive types can be passed through unmodified. output[attribute_name] = attribute elif isinstance(attribute, list): # Recursively deal with items in lists. output[attribute_name] = [ self.to_representation(item) for item in attribute ] elif isinstance(attribute, dict): # Recursively deal with items in dictionaries. output[attribute_name] = { str(key): self.to_representation(value) for key, value in attribute.items() } else: # Force anything else to its string representation. output[attribute_name] = str(attribute) return output ``` --- # Расширенное использование сериализатора ## Переопределение поведения сериализации и десериализации Если вам нужно изменить поведение сериализации или десериализации класса сериализатора, вы можете сделать это, переопределив методы `.to_representation()` или `.to_internal_value()`. Некоторые причины, по которым это может быть полезно, включают... * Добавление нового поведения для новых базовых классов сериализаторов. * Небольшое изменение поведения для существующего класса. * Улучшение производительности сериализации для часто используемой конечной точки API, которая возвращает много данных. Подписи для этих методов следующие: #### `to_representation(self, instance)`. Принимает экземпляр объекта, который требует сериализации, и должен вернуть примитивное представление. Обычно это означает возврат структуры встроенных в Python типов данных. Точные типы, которые могут быть обработаны, зависят от классов рендеринга, которые вы настроили для своего API. Может быть переопределена для изменения стиля представления. Например: ```python def to_representation(self, instance): """Convert `username` to lowercase.""" ret = super().to_representation(instance) ret['username'] = ret['username'].lower() return ret ``` #### `to_internal_value(self, data)`. Принимает невалидированные входящие данные в качестве входных и должен вернуть валидированные данные, которые будут доступны как `serializer.validated_data`. Возвращаемое значение также будет передано в методы `.create()` или `.update()`, если для класса сериализатора будет вызван `.save()`. Если какая-либо из валидаций не прошла, то метод должен вызвать `serializers.ValidationError(errors)`. Аргумент `errors` должен представлять собой словарь, отображающий имена полей (или `settings.NON_FIELD_ERRORS_KEY`) на список сообщений об ошибках. Если вам не нужно изменять поведение десериализации и вместо этого вы хотите обеспечить проверку на уровне объекта, рекомендуется переопределить метод [`.validate()`](#валидация-на-уровне-объекта). Аргумент `data`, передаваемый этому методу, обычно является значением `request.data`, поэтому тип данных, который он предоставляет, будет зависеть от классов парсера, которые вы настроили для своего API. ## Наследование сериализатора Подобно формам Django, вы можете расширять и повторно использовать сериализаторы с помощью наследования. Это позволяет вам объявить общий набор полей или методов в родительском классе, который затем может быть использован в нескольких сериализаторах. Например, ```python class MyBaseSerializer(Serializer): my_field = serializers.CharField() def validate_my_field(self, value): ... class MySerializer(MyBaseSerializer): ... ``` Как и классы `Model` и `ModelForm` в Django, внутренний класс `Meta` в сериализаторах не наследуется неявно от внутренних классов `Meta` своих родителей. Если вы хотите, чтобы класс `Meta` наследовался от родительского класса, вы должны сделать это явно. Например: ```python class AccountSerializer(MyBaseSerializer): class Meta(MyBaseSerializer.Meta): model = Account ``` Обычно мы рекомендуем *не* использовать наследование для внутренних классов Meta, а вместо этого объявлять все опции явно. Кроме того, следующие предостережения относятся к наследованию сериализаторов: * Применяются обычные правила разрешения имен Python. Если у вас есть несколько базовых классов, которые объявляют внутренний класс `Meta`, будет использоваться только первый класс. Это означает дочерний `Meta`, если он существует, иначе `Meta` первого родителя и т.д. * Можно декларативно удалить `Field`, унаследованный от родительского класса, указав значение `None` в подклассе. ```python class MyBaseSerializer(ModelSerializer): my_field = serializers.CharField() class MySerializer(MyBaseSerializer): my_field = None ``` Однако вы можете использовать эту технику только для отказа от поля, определенного декларативно родительским классом; это не помешает `ModelSerializer` сгенерировать поле по умолчанию. Чтобы отказаться от полей по умолчанию, смотрите [Указание, какие поля включать](#указание-какие-поля-включать). ## Динамическое изменение полей После инициализации сериализатора, к словарю полей, установленных в сериализаторе, можно получить доступ с помощью атрибута `.fields`. Доступ и изменение этого атрибута позволяет динамически модифицировать сериализатор. Изменение аргумента `fields` напрямую позволяет вам делать такие интересные вещи, как изменение аргументов полей сериализатора во время выполнения, а не в момент объявления сериализатора. ### Пример Например, если вы хотите иметь возможность установить, какие поля должны использоваться сериализатором в момент его инициализации, вы можете создать класс сериализатора следующим образом: ```python class DynamicFieldsModelSerializer(serializers.ModelSerializer): """ A ModelSerializer that takes an additional `fields` argument that controls which fields should be displayed. """ def __init__(self, *args, **kwargs): # Don't pass the 'fields' arg up to the superclass fields = kwargs.pop('fields', None) # Instantiate the superclass normally super().__init__(*args, **kwargs) if fields is not None: # Drop any fields that are not specified in the `fields` argument. allowed = set(fields) existing = set(self.fields) for field_name in existing - allowed: self.fields.pop(field_name) ``` Это позволит вам сделать следующее: ```python >>> class UserSerializer(DynamicFieldsModelSerializer): >>> class Meta: >>> model = User >>> fields = ['id', 'username', 'email'] >>> >>> print(UserSerializer(user)) {'id': 2, 'username': 'jonwatts', 'email': 'jon@example.com'} >>> >>> print(UserSerializer(user, fields=('id', 'email'))) {'id': 2, 'email': 'jon@example.com'} ``` ## Настройка полей по умолчанию REST framework 2 предоставил API, позволяющий разработчикам переопределять, как класс `ModelSerializer` будет автоматически генерировать набор полей по умолчанию. Этот API включал методы `.get_field()`, `.get_pk_field()` и другие. Поскольку сериализаторы были кардинально переработаны в версии 3.0, этот API больше не существует. Вы все еще можете изменять создаваемые поля, но вам придется обратиться к исходному коду, и имейте в виду, что если изменения, которые вы делаете, направлены против частных частей API, то они могут быть изменены. --- # Пакеты сторонних производителей Также доступны следующие пакеты сторонних производителей. ## Django REST marshmallow Пакет [django-rest-marshmallow](https://marshmallow-code.github.io/django-rest-marshmallow/) предоставляет альтернативную реализацию сериализаторов, используя библиотеку python [marshmallow](https://marshmallow.readthedocs.io/en/latest/). Он предоставляет тот же API, что и сериализаторы DRF, и может быть использован в качестве замены в некоторых случаях. ## Serpy Пакет [serpy](https://github.com/clarkduvall/serpy) - это альтернативная реализация сериализаторов, созданная для скорости. [Serpy](https://github.com/clarkduvall/serpy) сериализует сложные типы данных в простые нативные типы. Родные типы могут быть легко преобразованы в JSON или любой другой необходимый формат. ## MongoengineModelSerializer Пакет [django-rest-framework-mongoengine](https://github.com/umutbozkurt/django-rest-framework-mongoengine) предоставляет класс сериализатора `MongoEngineModelSerializer`, который поддерживает использование MongoDB в качестве уровня хранения данных для DRF. ## GeoFeatureModelSerializer Пакет [django-rest-framework-gis](https://github.com/djangonauts/django-rest-framework-gis) предоставляет класс сериализатора `GeoFeatureModelSerializer`, который поддерживает GeoJSON как для операций чтения, так и для записи. ## HStoreSerializer Пакет [django-rest-framework-hstore](https://github.com/djangonauts/django-rest-framework-hstore) предоставляет `HStoreSerializer` для поддержки поля модели [django-hstore](https://github.com/djangonauts/django-hstore) `DictionaryField` и его функции `chema-mode`. ## Dynamic REST Пакет [dynamic-rest](https://github.com/AltSchool/dynamic-rest) расширяет интерфейсы ModelSerializer и ModelViewSet, добавляя параметры запроса API для фильтрации, сортировки, включения/исключения всех полей и отношений, определенных вашими сериализаторами. ## Dynamic Fields Mixin Пакет [drf-dynamic-fields](https://github.com/dbrgn/drf-dynamic-fields) предоставляет миксин для динамического ограничения полей для сериализатора подмножеством, заданным параметром URL. ## DRF FlexFields Пакет [drf-flex-fields](https://github.com/rsinger86/drf-flex-fields) расширяет ModelSerializer и ModelViewSet для обеспечения широко используемой функциональности для динамической установки полей и расширения примитивных полей во вложенные модели, как из параметров URL, так и из определений класса вашего сериализатора. ## Serializer Extensions Пакет [django-rest-framework-serializer-extensions](https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions) предоставляет набор инструментов для DRY up ваших сериализаторов, позволяя определять поля на основе каждого представления/запроса. Поля могут быть внесены в белый или черный список, а дочерние сериализаторы могут быть расширены по желанию. ## HTML JSON Forms Пакет [html-json-forms](https://github.com/wq/html-json-forms) предоставляет алгоритм и сериализатор для обработки `
` в соответствии с (неактивной) [спецификацией HTML JSON Form](https://www.w3.org/TR/html-json-forms/). Сериализатор облегчает обработку произвольно вложенных структур JSON в HTML. Например, `` будет интерпретирован как `{"items": [{"id": "5"}]}`. ## DRF-Base64 [DRF-Base64](https://bitbucket.org/levit_scs/drf_base64) предоставляет набор сериализаторов полей и моделей, который обрабатывает загрузку файлов в base64-кодировке. ## QueryFields [djangorestframework-queryfields](https://djangorestframework-queryfields.readthedocs.io/) позволяет клиентам API указать, какие поля будут отправлены в ответе с помощью параметров запроса включения/исключения. ## DRF Writable Nested Пакет [drf-writable-nested](https://github.com/beda-software/drf-writable-nested) предоставляет записываемый сериализатор вложенных моделей, который позволяет создавать/обновлять модели с вложенными связанными данными. ## DRF Encrypt Content Пакет [drf-encrypt-content](https://github.com/oguzhancelikarslan/drf-encrypt-content) помогает вам шифровать данные, сериализованные через `ModelSerializer`. Он также содержит некоторые вспомогательные функции. Это поможет вам зашифровать ваши данные. ## Shapeless Serializers Пакет [drf-shapeless-serializers](https://github.com/khaledsukkar2/drf-shapeless-serializers) предоставляет возможности динамической настройки сериализатора, позволяя выбирать поля во время выполнения, переименовывать их, изменять атрибуты и настраивать вложенные отношения без создания нескольких классов сериализатора. Это помогает устранить шаблонные коды сериализатора и обеспечивает гибкие ответы API. ================================================ FILE: api-guide/settings.md ================================================ # Настройки > Пространства имен - это отличная идея - давайте делать их больше! > > - [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) Конфигурация для DRF находится в едином пространстве имен в настройках Django под названием `REST_FRAMEWORK`. Например, файл `settings.py` вашего проекта может содержать что-то вроде этого: ```python REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', ], 'DEFAULT_PARSER_CLASSES': [ 'rest_framework.parsers.JSONParser', ] } ``` ## Доступ к настройкам Если вам необходимо получить доступ к значениям настроек API DRF в вашем проекте, вам следует использовать объект `api_settings`. Например. ```python from rest_framework.settings import api_settings print(api_settings.DEFAULT_AUTHENTICATION_CLASSES) ``` Объект `api_settings` будет проверять наличие любых пользовательских настроек и в противном случае возвращаться к значениям по умолчанию. Любая настройка, использующая строковые пути импорта для ссылки на класс, будет автоматически импортировать и возвращать класс, на который ссылается, вместо строкового литерала. --- # API Reference ## Настройки политики API *Следующие настройки управляют основными политиками API и применяются к каждому представлению `APIView` на основе класса или `@api_view` на основе функции.* #### DEFAULT_RENDERER_CLASSES Список или кортеж классов рендереров, определяющий набор рендереров по умолчанию, которые могут быть использованы при возврате объекта `Response`. По умолчанию: ```python [ 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', ] ``` #### DEFAULT_PARSER_CLASSES Список или кортеж классов парсеров, определяющий набор парсеров по умолчанию, используемых при обращении к свойству `request.data`. По умолчанию: ```python [ 'rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser', 'rest_framework.parsers.MultiPartParser' ] ``` #### DEFAULT_AUTHENTICATION_CLASSES Список или кортеж классов аутентификации, определяющий набор аутентификаторов по умолчанию, используемых при обращении к свойствам `request.user` или `request.auth`. По умолчанию: ```python [ 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' ] ``` #### DEFAULT_PERMISSION_CLASSES Список или кортеж классов разрешений, который определяет набор разрешений по умолчанию, проверяемых при запуске представления. Разрешение должно быть предоставлено каждым классом в списке. По умолчанию: ```python [ 'rest_framework.permissions.AllowAny', ] ``` #### DEFAULT_THROTTLE_CLASSES Список или кортеж классов дросселей, который определяет набор дросселей по умолчанию, проверяемых при запуске представления. По умолчанию: `[]`. #### DEFAULT_CONTENT_NEGOTIATION_CLASS Класс согласования содержимого, который определяет, как выбирается рендерер для ответа, учитывая входящий запрос. По умолчанию: `'rest_framework.negotiation.DefaultContentNegotiation'`. #### DEFAULT_SCHEMA_CLASS Класс инспектора представлений, который будет использоваться для генерации схемы. По умолчанию: `'rest_framework.schemas.openapi.AutoSchema'`. --- ## Общие настройки представления *Следующие настройки управляют поведением общих представлений на основе классов.* #### DEFAULT_FILTER_BACKENDS Список классов бэкенда фильтра, которые должны использоваться для общей фильтрации. Если установлено значение `None`, то общая фильтрация отключена. #### DEFAULT_PAGINATION_CLASS Класс по умолчанию, используемый для пагинации наборов запросов. Если установлено значение `None`, пагинация по умолчанию отключена. Дополнительное руководство по [установке](pagination.md#установка-стиля-пагинации) и [изменению](pagination.md#изменение-стиля-пагинации) стиля пагинации см. в документации по пагинации. По умолчанию: `None` #### PAGE_SIZE Размер страницы по умолчанию, используемый для пагинации. Если установлено значение `None`, то по умолчанию пагинация отключена. По умолчанию: `None` ### SEARCH_PARAM Имя параметра запроса, который может быть использован для указания поискового термина, используемого `SearchFilter`. По умолчанию: `search`. #### ORDERING_PARAM Имя параметра запроса, который может быть использован для указания упорядочения результатов, возвращаемых `OrderingFilter`. По умолчанию: `ordering`. --- ## Настройки версий #### DEFAULT_VERSION Значение, которое должно использоваться для `request.version`, когда информация о версиях отсутствует. По умолчанию: `None` #### ALLOWED_VERSIONS Если задано, это значение ограничивает набор версий, которые могут быть возвращены схемой версий, и вызывает ошибку, если предоставленная версия не входит в этот набор. По умолчанию: `None` #### VERSION_PARAM Строка, которая должна использоваться для любых параметров версионирования, например, в типе медиа или параметрах запроса URL. По умолчанию: `'version'`. #### DEFAULT_VERSIONING_CLASS Схема версионирования, используемая по умолчанию. По умолчанию: `None` --- ## Настройки аутентификации *Следующие настройки управляют поведением неаутентифицированных запросов.* #### UNAUTHENTICATED_USER Класс, который должен использоваться для инициализации `request.user` для неаутентифицированных запросов. (Если аутентификация полностью удалена, например, путем удаления `django.contrib.auth` из `INSTALLED_APPS`, установите `UNAUTHENTICATED_USER` в `None`). По умолчанию: `django.contrib.auth.models.AnonymousUser`. #### UNAUTHENTICATED_TOKEN Класс, который должен использоваться для инициализации `request.auth` для неаутентифицированных запросов. По умолчанию: `Нет` --- ## Настройки тестов *Следующие настройки управляют поведением APIRequestFactory и APIClient.* #### TEST_REQUEST_DEFAULT_FORMAT Формат по умолчанию, который следует использовать при составлении тестовых запросов. Он должен совпадать с форматом одного из классов рендереров в настройке `TEST_REQUEST_RENDERER_CLASSES`. По умолчанию: `'multipart'`. #### TEST_REQUEST_RENDERER_CLASSES Классы рендереров, которые поддерживаются при построении тестовых запросов. Формат любого из этих классов рендереров может быть использован при построении тестового запроса, например: `client.post('/users', {'username': 'jamie'}, format='json')`. По умолчанию: ```python [ 'rest_framework.renderers.MultiPartRenderer', 'rest_framework.renderers.JSONRenderer' ] ``` --- ## Элементы управления генерацией схемы #### SCHEMA_COERCE_PATH_PK Если задано, то при генерации параметра пути к схеме идентификатор `'pk'` в URL conf сопоставляется с реальным именем поля. Обычно это `'id'`. Это дает более подходящее представление, поскольку "первичный ключ" - это деталь реализации, тогда как "идентификатор" - более общая концепция. По умолчанию: `True` #### SCHEMA_COERCE_METHOD_NAMES Если установлено, это используется для сопоставления внутренних имен методов набора представлений с именами внешних действий, используемых при генерации схемы. Это позволяет нам генерировать имена, более подходящие для внешнего представления, чем те, которые используются внутри кодовой базы. По умолчанию: `{'retrieve': 'read', 'destroy': 'delete'}` --- ## Контроль типа содержимого #### URL_FORMAT_OVERRIDE Имя параметра URL, который можно использовать для переопределения стандартного поведения заголовка согласования содержимого `Accept`, используя параметр запроса `format=...` в URL запроса. Например: `http://example.com/organizations/?format=csv` Если значение этого параметра равно `None`, то переопределение формата URL будет отключено. По умолчанию: `'format'`. #### FORMAT_SUFFIX_KWARG Имя параметра в URL conf, который может быть использован для обеспечения суффикса формата. Этот параметр применяется при использовании `format_suffix_patterns` для включения суффиксных шаблонов URL. Например: `http://example.com/organizations.csv/` По умолчанию: `'format'`. --- ## Форматирование даты и времени *Следующие параметры используются для управления тем, как представления даты и времени могут быть разобраны и отображены.* #### DATETIME_FORMAT Строка формата, которая должна использоваться по умолчанию для вывода полей сериализатора `DateTimeField`. Если `None`, то поля сериализатора `DateTimeField` будут возвращать объекты Python `datetime`, а кодировка времени будет определяться рендерером. Может быть любым из `None`, `'iso-8601'` или строкой Python [strftime format](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes). По умолчанию: `'iso-8601'`. #### DATETIME_INPUT_FORMATS Список форматных строк, которые должны использоваться по умолчанию при разборе входных данных для полей сериализатора `DateTimeField`. Может быть списком, включающим строку `'iso-8601'` или строки Python [strftime format](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes). По умолчанию: `['iso-8601']`. #### DATE_FORMAT Строка формата, которая должна использоваться по умолчанию для вывода полей сериализатора `DateField`. Если `None`, то поля сериализатора `DateField` будут возвращать объекты Python `date`, а кодировка даты будет определяться рендерером. Может быть любым из `None`, `'iso-8601'` или строкой Python [strftime format](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes). По умолчанию: `'iso-8601'`. #### DATE_INPUT_FORMATS Список форматных строк, которые должны использоваться по умолчанию при разборе входных данных для полей сериализатора `DateField`. Может быть списком, включающим строку `'iso-8601'` или строки Python [strftime format](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes). По умолчанию: `['iso-8601']`. #### TIME_FORMAT Строка формата, которая должна использоваться по умолчанию для вывода полей сериализатора `TimeField`. Если `None`, то поля сериализатора `TimeField` будут возвращать объекты Python `time`, а кодировка времени будет определяться рендерером. Может быть любым из `None`, `'iso-8601'` или строкой Python [strftime format](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes). По умолчанию: `'iso-8601'`. #### TIME_INPUT_FORMATS Список форматных строк, которые должны использоваться по умолчанию при разборе входных данных для полей сериализатора `TimeField`. Может быть списком, включающим строку `'iso-8601'` или строки Python [strftime format](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes). По умолчанию: `['iso-8601']`. #### DURATION_FORMAT Указывает формат по умолчанию, который следует использовать для рендеринга вывода полей сериализатора `DurationField`. Если `None`, то поля сериализатора `DurationField` будут возвращать объекты Python `timedelta`, а кодировка продолжительности будет определяться рендерером. Может быть любым из `None`, `'iso-8601'` или `'django'` (формат, принимаемый `django.utils.dateparse.parse_duration`). По умолчанию: `'django'` --- ## Кодировки #### UNICODE_JSON Если установлено значение `True`, ответы JSON будут разрешать использование символов юникода в ответах. Например: ```python {"unicode black star":"★"} ``` Если установлено значение `False`, в ответах JSON будут экранироваться неасксиальные символы, как показано ниже: ```python {"unicode black star":"\u2605"} ``` Оба стиля соответствуют [RFC 4627](https://www.ietf.org/rfc/rfc4627.txt) и являются синтаксически правильным JSON. Стиль unicode предпочтительнее, так как он более удобен при проверке ответов API. По умолчанию: `True` #### COMPACT_JSON Если установлено значение `True`, ответы JSON будут возвращать компактные представления, без пробелов после символов `':'` и `','`. Например: ```python {"is_admin":false,"email":"jane@example"} ``` Если установлено значение `False`, ответы JSON будут возвращать более подробное представление, как показано ниже: ```python {"is_admin": false, "email": "jane@example"} ``` По умолчанию возвращаются минифицированные ответы, в соответствии с [Heroku's API design guidelines](https://github.com/interagent/http-api-design#keep-json-minified-in-all-responses). По умолчанию: `True` #### STRICT_JSON Если установлено значение `True`, при рендеринге и разборе JSON будет использоваться только синтаксически правильный JSON, создавая исключение для расширенных значений float (`nan`, `inf`, `inf`), принимаемых модулем Python `json`. Это рекомендуемая настройка, так как эти значения обычно не поддерживаются. Например, ни Javascript `JSON.Parse`, ни PostgreSQL тип данных JSON не принимают эти значения. Если установлено значение `False`, рендеринг и парсинг JSON будут разрешительными. Однако эти значения все еще недействительны и должны быть специально обработаны в вашем коде. По умолчанию: `True` #### COERCE_DECIMAL_TO_STRING При возврате десятичных объектов в представлениях API, которые не поддерживают собственный десятичный тип, обычно лучше всего возвращать значение в виде строки. Это позволяет избежать потери точности, которая происходит при двоичной реализации с плавающей запятой. Если установлено значение `True`, сериализатор класса `DecimalField` будет возвращать строки вместо объектов `Decimal`. Если установлено значение `False`, сериализаторы будут возвращать объекты `Decimal`, которые кодировщик JSON по умолчанию будет возвращать как float. По умолчанию: `True` #### COERCE_BIGINT_TO_STRING При возвращении объектов biginteger в представлениях API, которые не поддерживают числа до 2^64, лучше всего возвращать значение в виде строки. Это позволяет избежать потери точности, которая происходит при реализации biginteger. При установке в `True`, сериализатор класса `BigIntegerField` (по умолчанию) будет возвращать строки вместо объектов `BigInteger`. При установке в `False`, сериализаторы будут возвращать объекты `BigInteger`, которые кодировщик JSON по умолчанию будет возвращать в виде чисел. По умолчанию: `False` --- ## Названия и описания представлений *Следующие настройки используются для создания названий и описаний представлений, которые используются в ответах на запросы `OPTIONS` и в API для просмотра.* #### VIEW_NAME_FUNCTION Строка, представляющая функцию, которая должна использоваться при генерации имен представлений. Это должна быть функция со следующей сигнатурой: ```python view_name(self) ``` * `self`: Экземпляр представления. Обычно функция name проверяет имя класса при генерации описательного имени, обращаясь к `self.__class__.__name__`. Если экземпляр представления наследует `ViewSet`, он может быть инициализирован с несколькими необязательными аргументами: * `name`: Имя, явно предоставленное представлению в наборе представлений. Обычно это значение должно использоваться как есть, если оно предоставлено. * `suffix`: Текст, используемый для различения отдельных представлений в наборе представлений. Этот аргумент является взаимоисключающим с `name`. * `detail`: Булево значение, отличающее индивидуальное представление в наборе представлений как "список" или "подробное представление". По умолчанию: `'rest_framework.views.get_view_name'`. #### VIEW_DESCRIPTION_FUNCTION Строка, представляющая функцию, которая должна использоваться при генерации описаний представлений. Этот параметр может быть изменен для поддержки стилей разметки, отличных от стандартного markdown. Например, вы можете использовать его для поддержки разметки `rst` в ваших документах представления, выводимых в Web-интерфейсе API. Это должна быть функция со следующей сигнатурой: ```python view_description(self, html=False) ``` * `self`: Экземпляр представления. Обычно функция описания проверяет строку документа класса при генерации описания, обращаясь к `self.__class__.__doc__`. * `html`: Булево значение, указывающее, требуется ли вывод HTML. `True` используется в API для просмотра, а `False` - при генерации ответов `OPTIONS`. Если экземпляр представления наследует `ViewSet`, он может быть инициализирован с несколькими необязательными аргументами: * `description`: Описание, явно предоставленное представлению в наборе представлений. Обычно оно устанавливается дополнительными `действиями` набора представлений и должно использоваться как есть. По умолчанию: `'rest_framework.views.get_view_description'`. ## HTML Select Field cutoffs Глобальные настройки для [выбора отсечений полей для визуализации реляционных полей](relations.md#выберите-отсечение-полей) в Web-интерфейсе API. #### HTML_SELECT_CUTOFF Глобальная настройка для значения `html_cutoff`. Должно быть целое число. По умолчанию: `1000` #### HTML_SELECT_CUTOFF_TEXT Строка, представляющая глобальную настройку для `html_cutoff_text`. По умолчанию: `'More than {count} items...'`. --- ## Разные настройки #### EXCEPTION_HANDLER Строка, представляющая функцию, которая должна быть использована при возврате ответа для любого данного исключения. Если функция возвращает `None`, будет выдана ошибка 500. Этот параметр может быть изменен для поддержки ответов на ошибки, отличных от ответов по умолчанию `{"detail": "Сбой..."}}` ответов. Например, вы можете использовать его для предоставления ответов API типа `{"errors": [{"message": "Failure...", "code": ""} ...]}`. Это должна быть функция со следующей сигнатурой: ```python exception_handler(exc, context) ``` * `exc`: Исключение. По умолчанию: `'rest_framework.views.exception_handler'`. #### NON_FIELD_ERRORS_KEY Строка, представляющая ключ, который следует использовать для ошибок сериализатора, которые не относятся к конкретному полю, а являются общими ошибками. По умолчанию: `'non_field_errors'`. #### URL_FIELD_NAME Строка, представляющая ключ, который должен использоваться для полей URL, генерируемых `HyperlinkedModelSerializer`. По умолчанию: `'url'` #### NUM_PROXIES Целое число, равное 0 или более, которое может использоваться для указания количества прокси-серверов приложений, за которыми работает API. Это позволяет дросселированию более точно определять IP-адреса клиентов. Если установлено значение `None`, то классы дросселирования будут использовать менее строгое сопоставление IP-адресов. По умолчанию: `None` ================================================ FILE: api-guide/status-codes.md ================================================ # Коды состояния > 418 Я чайник - Любая попытка сварить кофе с помощью чайника должна привести к коду ошибки "418 I'm a teapot". Результирующее тело сущности МОЖЕТ быть коротким и крепким. > > - [RFC 2324](https://www.ietf.org/rfc/rfc2324.txt), Hyper Text Coffee Pot Control Protocol Не рекомендуется использовать в ответах "голые" коды состояния. DRF включает набор именованных констант, которые вы можете использовать, чтобы сделать ваш код более очевидным и читаемым. ```python from rest_framework import status from rest_framework.response import Response def empty_view(self): content = {'please move along': 'nothing to see here'} return Response(content, status=status.HTTP_404_NOT_FOUND) ``` Полный набор кодов состояния HTTP, включенных в модуль `status`, приведен ниже. Модуль также включает набор вспомогательных функций для проверки того, находится ли код состояния в заданном диапазоне. ```python from rest_framework import status from rest_framework.test import APITestCase class ExampleTestCase(APITestCase): def test_url_root(self): url = reverse('index') response = self.client.get(url) self.assertTrue(status.is_success(response.status_code)) ``` Более подробную информацию о правильном использовании кодов состояния HTTP смотрите в [RFC 2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) и [RFC 6585](https://tools.ietf.org/html/rfc6585). ## Информационный - 1xx Этот класс кода состояния указывает на предварительный ответ. По умолчанию в DRF не используются коды состояния 1xx. ```python HTTP_100_CONTINUE HTTP_101_SWITCHING_PROTOCOLS HTTP_102_PROCESSING HTTP_103_EARLY_HINTS ``` ## Успешно - 2xx Этот класс кода состояния указывает на то, что запрос клиента был успешно получен, понят и принят. ```python HTTP_200_OK HTTP_201_CREATED HTTP_202_ACCEPTED HTTP_203_NON_AUTHORITATIVE_INFORMATION HTTP_204_NO_CONTENT HTTP_205_RESET_CONTENT HTTP_206_PARTIAL_CONTENT HTTP_207_MULTI_STATUS HTTP_208_ALREADY_REPORTED HTTP_226_IM_USED ``` ## Перенаправление - 3xx Этот класс кода состояния указывает на то, что агенту пользователя необходимо предпринять дополнительные действия для выполнения запроса. ```python HTTP_300_MULTIPLE_CHOICES HTTP_301_MOVED_PERMANENTLY HTTP_302_FOUND HTTP_303_SEE_OTHER HTTP_304_NOT_MODIFIED HTTP_305_USE_PROXY HTTP_306_RESERVED HTTP_307_TEMPORARY_REDIRECT HTTP_308_PERMANENT_REDIRECT ``` ## Ошибка клиента - 4xx Код состояния класса 4xx предназначен для случаев, когда клиент, похоже, ошибся. За исключением ответа на запрос HEAD, сервер ДОЛЖЕН включать объект, содержащий объяснение ситуации с ошибкой, а также то, является ли она временной или постоянной. ```python HTTP_400_BAD_REQUEST HTTP_401_UNAUTHORIZED HTTP_402_PAYMENT_REQUIRED HTTP_403_FORBIDDEN HTTP_404_NOT_FOUND HTTP_405_METHOD_NOT_ALLOWED HTTP_406_NOT_ACCEPTABLE HTTP_407_PROXY_AUTHENTICATION_REQUIRED HTTP_408_REQUEST_TIMEOUT HTTP_409_CONFLICT HTTP_410_GONE HTTP_411_LENGTH_REQUIRED HTTP_412_PRECONDITION_FAILED HTTP_413_REQUEST_ENTITY_TOO_LARGE HTTP_414_REQUEST_URI_TOO_LONG HTTP_415_UNSUPPORTED_MEDIA_TYPE HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE HTTP_417_EXPECTATION_FAILED HTTP_421_MISDIRECTED_REQUEST HTTP_422_UNPROCESSABLE_ENTITY HTTP_423_LOCKED HTTP_424_FAILED_DEPENDENCY HTTP_425_TOO_EARLY HTTP_426_UPGRADE_REQUIRED HTTP_428_PRECONDITION_REQUIRED HTTP_429_TOO_MANY_REQUESTS HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS ``` ## Ошибка сервера - 5xx Коды состояния ответа, начинающиеся с цифры "5", указывают на случаи, когда сервер знает, что он ошибся или не в состоянии выполнить запрос. За исключением ответа на запрос HEAD, сервер ДОЛЖЕН включать объект, содержащий объяснение ситуации с ошибкой, а также то, является ли она временной или постоянной. ```python HTTP_500_INTERNAL_SERVER_ERROR HTTP_501_NOT_IMPLEMENTED HTTP_502_BAD_GATEWAY HTTP_503_SERVICE_UNAVAILABLE HTTP_504_GATEWAY_TIMEOUT HTTP_505_HTTP_VERSION_NOT_SUPPORTED HTTP_506_VARIANT_ALSO_NEGOTIATES HTTP_507_INSUFFICIENT_STORAGE HTTP_508_LOOP_DETECTED HTTP_509_BANDWIDTH_LIMIT_EXCEEDED HTTP_510_NOT_EXTENDED HTTP_511_NETWORK_AUTHENTICATION_REQUIRED ``` ## Вспомогательные функции Для определения категории кода ответа доступны следующие вспомогательные функции. ```python is_informational() # 1xx is_success() # 2xx is_redirect() # 3xx is_client_error() # 4xx is_server_error() # 5xx ``` ================================================ FILE: api-guide/testing.md ================================================ # Тестирование > Код без тестов сломан по умолчанию. > > - [Джейкоб Каплан-Мосс](https://jacobian.org/writing/django-apps-with-buildout/#s-create-a-test-wrapper) DRF включает несколько вспомогательных классов, которые расширяют существующую тестовую структуру Django и улучшают поддержку выполнения API-запросов. # APIRequestFactory Расширяет [существующий в Django класс `RequestFactory`](https://docs.djangoproject.com/en/stable/topics/testing/advanced/#django.test.client.RequestFactory). ## Создание тестовых запросов Класс `APIRequestFactory` поддерживает почти такой же API, как и стандартный класс Django `RequestFactory`. Это означает, что все стандартные методы `.get()`, `.post()`, `.put()`, `.patch()`, `.delete()`, `.head()` и `.options()` доступны. ```python from rest_framework.test import APIRequestFactory # Using the standard RequestFactory API to create a form POST request factory = APIRequestFactory() request = factory.post('/notes/', {'title': 'new idea'}) # Using the standard RequestFactory API to encode JSON data request = factory.post('/notes/', {'title': 'new idea'}, content_type='application/json') ``` #### Использование аргумента `format` Методы, создающие тело запроса, такие как `post`, `put` и `patch`, включают аргумент `format`, который позволяет легко генерировать запросы с использованием широкого набора форматов запросов. При использовании этого аргумента фабрика выберет соответствующий рендерер и его сконфигурированный `content_type`. Например: ```python # Create a JSON POST request factory = APIRequestFactory() request = factory.post('/notes/', {'title': 'new idea'}, format='json') ``` По умолчанию доступны форматы `'multipart'` и `'json'`. Для совместимости с существующей в Django `RequestFactory` по умолчанию используется формат `'multipart'`. Чтобы поддерживать более широкий набор форматов запросов или изменить формат по умолчанию, [см. раздел конфигурации](#Конфигурация). #### Явное кодирование тела запроса Если вам нужно явно закодировать тело запроса, вы можете сделать это, установив флаг `content_type`. Например: ```python request = factory.post('/notes/', yaml.dump({'title': 'new idea'}), content_type='application/yaml') ``` #### PUT и PATCH с данными формы Стоит отметить одно отличие между `RequestFactory` Django и `APIRequestFactory` DRF в том, что данные многочастной формы будут закодированы для методов, отличных от `.post()`. Например, используя `APIRequestFactory`, вы можете сделать запрос формы PUT следующим образом: ```python factory = APIRequestFactory() request = factory.put('/notes/547/', {'title': 'remember to email dave'}) ``` Используя `RequestFactory` от Django, вам придется явно кодировать данные самостоятельно: ```python from django.test.client import encode_multipart, RequestFactory factory = RequestFactory() data = {'title': 'remember to email dave'} content = encode_multipart('BoUnDaRyStRiNg', data) content_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg' request = factory.put('/notes/547/', content, content_type=content_type) ``` ## Принудительная аутентификация При тестировании представлений непосредственно с помощью фабрики запросов часто бывает удобно иметь возможность напрямую аутентифицировать запрос, а не создавать правильные учетные данные для аутентификации. Чтобы принудительно аутентифицировать запрос, используйте метод `force_authenticate()`. ```python from rest_framework.test import force_authenticate factory = APIRequestFactory() user = User.objects.get(username='olivia') view = AccountDetail.as_view() # Make an authenticated request to the view... request = factory.get('/accounts/django-superstars/') force_authenticate(request, user=user) response = view(request) ``` Если вы хотите протестировать запрос, связанный с объектом «Request» фреймворка REST, вам необходимо сначала вручную преобразовать его: ```python class DummyView(APIView): ... factory = APIRequestFactory() request = factory.get('/', {'demo': 'test'}) drf_request = DummyView().initialize_request(request) assert drf_request.query_params == {'demo': ['test']} request = factory.post('/', {'example': 'test'}) drf_request = DummyView().initialize_request(request) assert drf_request.data.get('example') == 'test' ``` Сигнатура для метода - `force_authenticate(request, user=None, token=None)`. При выполнении вызова может быть задан пользователь и токен или оба. Например, при принудительной аутентификации с помощью токена вы можете сделать что-то вроде следующего: ```python user = User.objects.get(username='olivia') request = factory.get('/accounts/django-superstars/') force_authenticate(request, user=user, token=user.auth_token) ``` --- **Примечание**: `force_authenticate` напрямую устанавливает `request.user` в экземпляр `user` в памяти. Если вы повторно используете один и тот же экземпляр `user` в нескольких тестах, которые обновляют сохраненное состояние `user`, вам может понадобиться вызывать [`refresh_from_db()`](https://docs.djangoproject.com/en/stable/ref/models/instances/#django.db.models.Model.refresh_from_db) между тестами. --- **Примечание**: При использовании `APIRequestFactory`, возвращаемый объект - это стандартный `HttpRequest` Django, а не объект `Request` DRF, который генерируется только после вызова представления. Это означает, что установка атрибутов непосредственно на объект запроса не всегда может иметь ожидаемый эффект. Например, установка `.token` напрямую не будет иметь никакого эффекта, а установка `.user` напрямую будет работать только при использовании сеансовой аутентификации. ```python # Request will only authenticate if `SessionAuthentication` is in use. request = factory.get('/accounts/django-superstars/') request.user = user response = view(request) ``` --- ## Принудительная проверка CSRF По умолчанию запросы, созданные с помощью `APIRequestFactory`, не будут проходить проверку CSRF при передаче в представление DRF. Если вам необходимо явно включить проверку CSRF, вы можете сделать это, установив флаг `enforce_csrf_checks` при инстанцировании фабрики. ```python factory = APIRequestFactory(enforce_csrf_checks=True) ``` --- **Примечание**: Стоит отметить, что стандартная фабрика запросов Django `RequestFactory` не должна включать эту опцию, потому что при использовании обычного Django проверка CSRF происходит в промежуточном ПО, которое не запускается при тестировании представлений напрямую. При использовании DRF проверка CSRF происходит внутри представления, поэтому в фабрике запросов необходимо отключить проверку CSRF на уровне представления. --- # APIClient Расширяет [существующий в Django класс `Client`](https://docs.djangoproject.com/en/stable/topics/testing/tools/#the-test-client). ## Выполнение запросов Класс `APIClient` поддерживает тот же интерфейс запросов, что и стандартный класс Django `Client`. Это означает, что стандартные методы `.get()`, `.post()`, `.put()`, `.patch()`, `.delete()`, `.head()` и `.options()` доступны. Например: ```python from rest_framework.test import APIClient client = APIClient() client.post('/notes/', {'title': 'new idea'}, format='json') ``` Чтобы поддерживать более широкий набор форматов запросов или изменить формат по умолчанию, [см. раздел конфигурации](#Конфигурация). ## Аутентификация #### .login(**kwargs) Метод `login` функционирует точно так же, как и в обычном классе Django `Client`. Это позволяет вам аутентифицировать запросы к любым представлениям, которые включают `SessionAuthentication`. ```python # Make all requests in the context of a logged in session. client = APIClient() client.login(username='lauren', password='secret') ``` Чтобы выйти из системы, вызовите метод `logout`, как обычно. ```python # Log out client.logout() ``` Метод `login` подходит для тестирования API, использующих сеансовую аутентификацию, например, веб-сайтов, включающих AJAX-взаимодействие с API. #### .credentials(**kwargs) Метод `credentials` можно использовать для установки заголовков, которые затем будут включены во все последующие запросы тестового клиента. ```python from rest_framework.authtoken.models import Token from rest_framework.test import APIClient # Include an appropriate `Authorization:` header on all requests. token = Token.objects.get(user__username='lauren') client = APIClient() client.credentials(HTTP_AUTHORIZATION='Token ' + token.key) ``` Обратите внимание, что вызов `credentials` во второй раз перезаписывает все существующие учетные данные. Вы можете удалить все существующие учетные данные, вызвав метод без аргументов. ```python # Stop including any credentials client.credentials() ``` Метод `credentials` подходит для тестирования API, требующих заголовков аутентификации, таких как базовая аутентификация, аутентификация OAuth1a и OAuth2, а также простые схемы аутентификации токенов. #### .force_authenticate(user=None, token=None) Иногда вы можете захотеть полностью обойти аутентификацию и заставить все запросы тестового клиента автоматически рассматриваться как аутентифицированные. Это может быть полезным сокращением, если вы тестируете API, но не хотите создавать действительные учетные данные аутентификации для выполнения тестовых запросов. ```python user = User.objects.get(username='lauren') client = APIClient() client.force_authenticate(user=user) ``` Чтобы не аутентифицировать последующие запросы, вызовите `force_authenticate`, установив для пользователя и/или токена значение `None`. ```python client.force_authenticate(user=None) ``` ## Проверка CSRF По умолчанию проверка CSRF не применяется при использовании `APIClient`. Если вам необходимо явно включить проверку CSRF, вы можете сделать это, установив флаг `enforce_csrf_checks` при инстанцировании клиента. ```python client = APIClient(enforce_csrf_checks=True) ``` Обычно, проверка CSRF будет применяться только к любым аутентифицированным в сеансе представлениям. Это означает, что проверка CSRF будет происходить только в том случае, если клиент вошел в систему, вызвав `login()`. --- # RequestsClient DRF также включает клиент для взаимодействия с вашим приложением с помощью популярной библиотеки Python, `requests`. Это может быть полезно, если: * Вы предполагаете взаимодействовать с API в основном из другого сервиса Python и хотите протестировать сервис на том же уровне, который будет видеть клиент. * Вы хотите написать тесты таким образом, чтобы их можно было запускать в среде постановки или в реальном времени. (См. раздел "Живые тесты" ниже). Это предоставляет точно такой же интерфейс, как если бы вы использовали сессию запросов напрямую. ```python from rest_framework.test import RequestsClient client = RequestsClient() response = client.get('http://testserver/users/') assert response.status_code == 200 ``` Обратите внимание, что клиент запросов требует передачи полностью определенных URL-адресов. ## RequestsClient и работа с базой данных Класс `RequestsClient` полезен, если вы хотите написать тесты, которые взаимодействуют только с интерфейсом сервиса. Это немного строже, чем использование стандартного тестового клиента Django, поскольку это означает, что все взаимодействия должны осуществляться через API. Если вы используете `RequestsClient`, вам нужно убедиться, что установка тестов и утверждения результатов выполняются как обычные вызовы API, а не взаимодействуют с моделями базы данных напрямую. Например, вместо того чтобы проверять, что `Customer.objects.count() == 3`, вы должны перечислить конечную точку `customers` и убедиться, что она содержит три записи. ## Заголовки и аутентификация Пользовательские заголовки и учетные данные аутентификации могут быть предоставлены так же, как и [при использовании стандартного экземпляра `requests.Session`](https://requests.readthedocs.io/en/latest/user/advanced/#session-objects). ```python from requests.auth import HTTPBasicAuth client.auth = HTTPBasicAuth('user', 'pass') client.headers.update({'x-test': 'true'}) ``` ## CSRF Если вы используете `SessionAuthentication`, то вам необходимо включить CSRF-токен для любых запросов `POST`, `PUT`, `PATCH` или `DELETE`. Вы можете сделать это, следуя той же схеме, которую использует клиент на базе JavaScript. Сначала сделайте запрос `GET`, чтобы получить маркер CSRF, а затем цкажите этот токен в следующем запросе. Например... ```python client = RequestsClient() # Obtain a CSRF token. response = client.get('http://testserver/homepage/') assert response.status_code == 200 csrftoken = response.cookies['csrftoken'] # Interact with the API. response = client.post('http://testserver/organizations/', json={ 'name': 'MegaCorp', 'status': 'active' }, headers={'X-CSRFToken': csrftoken}) assert response.status_code == 200 ``` ## Живые тесты При тщательном использовании и `RequestsClient`, и `CoreAPIClient` дают возможность писать тесты, которые можно запускать как в процессе разработки, так и непосредственно на build сервере или в production среде. Использование этого стиля для создания базовых тестов нескольких основных частей функциональности является мощным способом проверки вашего живого сервиса. Это может потребовать некоторого внимания к `setup` и `teardown`, чтобы убедиться, что тесты выполняются таким образом, что они не влияют непосредственно на данные клиентов. --- # CoreAPIClient `CoreAPIClient` позволяет вам взаимодействовать с API с помощью клиентской библиотеки Python `coreapi`. ```python # Fetch the API schema client = CoreAPIClient() schema = client.get('http://testserver/schema/') # Create a new organization params = {'name': 'MegaCorp', 'status': 'active'} client.action(schema, ['organizations', 'create'], params) # Ensure that the organization exists in the listing data = client.action(schema, ['organizations', 'list']) assert(len(data) == 1) assert(data == [{'name': 'MegaCorp', 'status': 'active'}]) ``` ## Заголовки и аутентификация Пользовательские заголовки и аутентификация могут использоваться с `CoreAPIClient` так же, как и с `RequestsClient`. ```python from requests.auth import HTTPBasicAuth client = CoreAPIClient() client.session.auth = HTTPBasicAuth('user', 'pass') client.session.headers.update({'x-test': 'true'}) ``` --- # Тесты API DRF включает следующие классы тестов, которые являются зеркальным отражением существующих [Django's test case classes](https://docs.djangoproject.com/en/stable/topics/testing/tools/#provided-test-case-classes), но используют `APIClient` вместо Django's default `Client`. * `APISimpleTestCase`. * `APITransactionTestCase` * `APITestCase` * `APILiveServerTestCase` ## Пример Вы можете использовать любой из классов тестов DRF так же, как и обычные классы тестов Django. Атрибутом `self.client` будет экземпляр `APIClient`. ```python from django.urls import reverse from rest_framework import status from rest_framework.test import APITestCase from myproject.apps.core.models import Account class AccountTests(APITestCase): def test_create_account(self): """ Ensure we can create a new account object. """ url = reverse('account-list') data = {'name': 'DabApps'} response = self.client.post(url, data, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(Account.objects.count(), 1) self.assertEqual(Account.objects.get().name, 'DabApps') ``` --- # URLPatternsTestCase DRF также предоставляет класс тестов для изоляции `urlpatterns` на основе каждого класса. Обратите внимание, что он наследуется от Django `SimpleTestCase`, и, скорее всего, его придется смешивать с другим классом тестов. ## Пример ```python from django.urls import include, path, reverse from rest_framework import status from rest_framework.test import APITestCase, URLPatternsTestCase class AccountTests(APITestCase, URLPatternsTestCase): urlpatterns = [ path('api/', include('api.urls')), ] def test_create_account(self): """ Ensure we can create a new account object. """ url = reverse('account-list') response = self.client.get(url, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) ``` --- # Тестирование ответов ## Проверка данных ответа При проверке достоверности тестовых ответов часто удобнее проверять данные, на основе которых был создан ответ, чем проверять полностью отрисованный ответ. Например, проще проверить `response.data`: ```python response = self.client.get('/users/4/') self.assertEqual(response.data, {'id': 4, 'username': 'lauren'}) ``` Вместо того чтобы проверять результат разбора `response.content`: ```python response = self.client.get('/users/4/') self.assertEqual(json.loads(response.content), {'id': 4, 'username': 'lauren'}) ``` ## Ответы на рендеринг Если вы тестируете представления напрямую, используя `APIRequestFactory`, возвращаемые ответы еще не будут отрендерены, поскольку рендеринг ответов шаблона выполняется внутренним циклом запроса-ответа Django. Чтобы получить доступ к `response.content`, вам сначала нужно отрендерить ответ. ```python view = UserDetail.as_view() request = factory.get('/users/4') response = view(request, pk='4') response.render() # Cannot access `response.content` without this. self.assertEqual(response.content, '{"username": "lauren", "id": 4}') ``` --- # Конфигурация ## Установка формата по умолчанию Формат по умолчанию, используемый для выполнения тестовых запросов, можно установить с помощью ключа настройки `TEST_REQUEST_DEFAULT_FORMAT`. Например, чтобы всегда использовать JSON для тестовых запросов по умолчанию вместо стандартных multipart form запросов, установите следующее в файле `settings.py`: ```python REST_FRAMEWORK = { ... 'TEST_REQUEST_DEFAULT_FORMAT': 'json' } ``` ## Установка доступных форматов Если вам нужно протестировать запросы, использующие не многочастичные или json-запросы, вы можете сделать это, установив параметр `TEST_REQUEST_RENDERER_CLASSES`. Например, чтобы добавить поддержку использования `format='html'` в тестовых запросах, в файле `settings.py` можно сделать что-то вроде этого. ```python REST_FRAMEWORK = { ... 'TEST_REQUEST_RENDERER_CLASSES': [ 'rest_framework.renderers.MultiPartRenderer', 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.TemplateHTMLRenderer' ] } ``` ================================================ FILE: api-guide/throttling.md ================================================ # Дросселирование > HTTP/1.1 420 Повышение спокойствия > > [Twitter API ограничение скорости ответа](https://developer.twitter.com/en/docs/basics/rate-limiting) Дросселирование аналогично [permissions](permissions.md), поскольку оно определяет, должен ли запрос быть авторизован. Дроссели обозначают временное состояние и используются для контроля скорости запросов, которые клиенты могут делать к API. Как и в случае с разрешениями, можно использовать несколько дросселей. Ваш API может иметь ограничительный дроссель для неаутентифицированных запросов и менее ограничительный дроссель для аутентифицированных запросов. Еще один сценарий, в котором вам может понадобиться использовать несколько дросселей, - это если вам нужно наложить различные ограничения на разные части API, поскольку некоторые сервисы являются особенно ресурсоемкими. Несколько дросселей также можно использовать, если вы хотите наложить дросселирование как на скорость разрыва, так и на скорость устойчивого дросселирования. Например, вы можете ограничить пользователя максимум 60 запросами в минуту и 1000 запросами в день. Дроссели не обязательно относятся только к запросам на ограничение скорости. Например, служба хранения данных может также нуждаться в ограничении пропускной способности, а платная служба данных может захотеть ограничить доступ к определенному количеству записей. **Дросселирование на уровне приложения, которое обеспечивает DRF, не следует рассматривать как меру безопасности или защиту от перебора или атак типа "отказ в обслуживании". Намеренно злоумышленники всегда смогут подделать IP-адреса. В дополнение к этому, встроенная реализация дросселирования реализована с использованием кэш-фреймворка Django и использует неатомарные операции для определения скорости запросов, что иногда может привести к некоторой нечеткости.** **Дросселирование на уровне приложений, предоставляемое DRF, предназначено для реализации таких политик, как различные бизнес-уровни и базовая защита от чрезмерного использования услуг.** ## Как определяется дросселирование Как и в случае с разрешениями и аутентификацией, дросселирование в DRF всегда определяется как список классов. Перед запуском основной части представления проверяется каждый дроссель в списке. Если какая-либо проверка дросселя не прошла, будет вызвано исключение `exceptions.Throttled`, и основное тело представления не будет запущено. ## Установка политики дросселирования Политика дросселирования по умолчанию может быть установлена глобально, с помощью параметров `DEFAULT_THROTTLE_CLASSES` и `DEFAULT_THROTTLE_RATES`. Например. ```python REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.AnonRateThrottle', 'rest_framework.throttling.UserRateThrottle' ], 'DEFAULT_THROTTLE_RATES': { 'anon': '100/day', 'user': '1000/day' } } ``` Показатели, используемые в `DEFAULT_THROTTLE_RATES`, могут быть заданы за период в секунду, минуту, час или день. Период должен быть указан после разделителя `/`, используя `s`, `m`, `h` или `d`, соответственно. Для большей ясности допускается использование расширенных единиц измерения, таких как `second`, `minute`, `hour`, `day` или даже сокращений `sec`, `min`, `hr`, поскольку только первый символ имеет значение для идентификации показателя. Вы также можете установить политику дросселирования на основе каждого представления или каждого набора представлений, используя представления на основе класса `APIView`. ```python from rest_framework.response import Response from rest_framework.throttling import UserRateThrottle from rest_framework.views import APIView class ExampleView(APIView): throttle_classes = [UserRateThrottle] def get(self, request, format=None): content = { 'status': 'request was permitted' } return Response(content) ``` Если вы используете декоратор `@api_view` с представлениями, основанными на функциях, вы можете использовать следующий декоратор. ```python @api_view(['GET']) @throttle_classes([UserRateThrottle]) def example_view(request, format=None): content = { 'status': 'request was permitted' } return Response(content) ``` Также можно установить классы дросселей для маршрутов, которые создаются с помощью декоратора `@action`. Установленные таким образом классы дросселирования будут переопределять любые настройки классов на уровне набора представлений. ```python @action(detail=True, methods=["post"], throttle_classes=[UserRateThrottle]) def example_adhoc_method(request, pk=None): content = { 'status': 'request was permitted' } return Response(content) ``` ## Как определяются клиенты HTTP-заголовок `X-Forwarded-For` и WSGI-переменная `REMOTE_ADDR` используются для уникальной идентификации IP-адресов клиентов для дросселирования. Если заголовок `X-Forwarded-For` присутствует, то он будет использоваться, иначе будет использоваться значение переменной `REMOTE_ADDR` из среды WSGI. Если вам необходимо строго идентифицировать уникальные IP-адреса клиентов, вам нужно сначала настроить количество прокси-серверов приложений, за которыми работает API, установив параметр `NUM_PROXIES`. Это значение должно быть целым числом, равным нулю или больше. Если значение ненулевое, то IP-адрес клиента будет идентифицироваться как последний IP-адрес в заголовке `X-Forwarded-For`, после того как IP-адреса прокси приложений будут исключены. Если установлено в ноль, то значение `REMOTE_ADDR` всегда будет использоваться в качестве идентифицирующего IP-адреса. Важно понимать, что если вы настроите параметр `NUM_PROXIES`, то все клиенты за уникальным [NAT-ом](https://en.wikipedia.org/wiki/Network_address_translation) шлюзом будут рассматриваться как один клиент. Дополнительную информацию о том, как работает заголовок `X-Forwarded-For` и как определить IP удаленного клиента, можно найти [здесь](http://oxpedia.org/wiki/index.php?title=AppSuite:Grizzly#Multiple_Proxies_in_front_of_the_cluster). ## Настройка кэша Классы дросселирования, предоставляемые DRF, используют бэкенд кэша Django. Вы должны убедиться, что установили соответствующие настройки [cache settings](https://docs.djangoproject.com/en/stable/ref/settings/#caches). Значение по умолчанию бэкенда `LocMemCache` должно быть приемлемым для простых настроек. Более подробную информацию можно найти в [документации по кэшу](https://docs.djangoproject.com/en/stable/topics/cache/#setting-up-the-cache) Django. Если вам нужно использовать кэш, отличный от `'default'`, вы можете сделать это, создав пользовательский класс дросселя и установив атрибут `cache`. Например: ```python from django.core.cache import caches class CustomAnonRateThrottle(AnonRateThrottle): cache = caches['alternate'] ``` Вам нужно будет не забыть также установить ваш пользовательский класс дросселя в ключе настроек `'DEFAULT_THROTTLE_CLASSES'` или с помощью атрибута представления `throttle_classes`. ## Заметка о параллелизме Встроенные реализации дросселей открыты для [условий гонки](https://en.wikipedia.org/wiki/Race_condition#Data_race), поэтому при высоком параллелизме они могут пропустить несколько лишних запросов. Если ваш проект полагается на гарантию количества запросов во время одновременных запросов, вам необходимо реализовать свой собственный класс дросселя. Более подробную информацию см. в [issue #5181][https://github.com/encode/django-rest-framework/issues/5181]. --- # API Reference ## AnonRateThrottle Дроссель `AnonRateThrottle` будет дросселировать только неаутентифицированных пользователей. IP-адрес входящего запроса используется для генерации уникального ключа для дросселирования. Допустимая скорость запроса определяется по одному из следующих параметров (в порядке предпочтения). * Свойство `rate` класса, которое может быть предоставлено путем переопределения `AnonRateThrottle` и установки свойства. * Настройка `DEFAULT_THROTTLE_RATES['anon']`. `AnonRateThrottle` подходит, если вы хотите ограничить скорость запросов от неизвестных источников. ## UserRateThrottle Дроссель `UserRateThrottle` будет дросселировать пользователей до заданной скорости запросов через API. Идентификатор пользователя используется для генерации уникального ключа для дросселирования. Неаутентифицированные запросы будут возвращаться к использованию IP-адреса входящего запроса для генерации уникального ключа для дросселирования. Допустимая скорость запроса определяется по одному из следующих параметров (в порядке предпочтения). * Свойство `rate` класса, которое может быть предоставлено путем переопределения `UserRateThrottle` и установки свойства. * Настройка `DEFAULT_THROTTLE_RATES['user']`. API может иметь несколько `UserRateThrottles` одновременно. Для этого переопределите `UserRateThrottle` и установите уникальный `'scope'` для каждого класса. Например, несколько пользовательских дросселей могут быть реализованы с помощью следующих классов... ```python class BurstRateThrottle(UserRateThrottle): scope = 'burst' class SustainedRateThrottle(UserRateThrottle): scope = 'sustained' ``` ...и следующие настройки. ```python REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': [ 'example.throttles.BurstRateThrottle', 'example.throttles.SustainedRateThrottle' ], 'DEFAULT_THROTTLE_RATES': { 'burst': '60/min', 'sustained': '1000/day' } } ``` `UserRateThrottle` подходит, если вам нужны простые глобальные ограничения скорости для каждого пользователя. ## ScopedRateThrottle Класс `ScopedRateThrottle` можно использовать для ограничения доступа к определенным частям API. Этот дроссель будет применяться, только если представление, к которому осуществляется доступ, включает свойство `.throttle_scope`. Уникальный ключ дросселя будет сформирован путем соединения `'scope'` запроса с уникальным идентификатором пользователя или IP-адресом. Допустимая скорость запроса определяется настройкой `DEFAULT_THROTTLE_RATES`, используя ключ из `'scope'` запроса. Например, учитывая следующие представления... ```python class ContactListView(APIView): throttle_scope = 'contacts' ... class ContactDetailView(APIView): throttle_scope = 'contacts' ... class UploadView(APIView): throttle_scope = 'uploads' ... ``` ...и следующие настройки. ```python REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.ScopedRateThrottle', ], 'DEFAULT_THROTTLE_RATES': { 'contacts': '1000/day', 'uploads': '20/day' } } ``` Запросы пользователей к `ContactListView` или `ContactDetailView` будут ограничены до 1000 запросов в день. Запросы пользователей к `UploadView` будут ограничены 20 запросами в день. --- # Пользовательские дроссели Чтобы создать пользовательский дроссель, переопределите `BaseThrottle` и реализуйте `.allow_request(self, request, view)`. Метод должен возвращать `True`, если запрос должен быть разрешен, и `False` в противном случае. По желанию вы также можете переопределить метод `.wait()`. Если он реализован, `.wait()` должен возвращать рекомендуемое количество секунд ожидания перед попыткой следующего запроса или `None`. Метод `.wait()` будет вызван только в том случае, если `.allow_request()` ранее вернул `False`. Если реализован метод `.wait()` и запрос дросселируется, то в ответ будет включен заголовок `Retry-After`. ## Пример Ниже приведен пример дросселирования скорости, которое будет случайным образом дросселировать 1 из каждых 10 запросов. ```python import random class RandomRateThrottle(throttling.BaseThrottle): def allow_request(self, request, view): return random.randint(1, 10) != 1 ``` ================================================ FILE: api-guide/validators.md ================================================ --- источник: - validators.py --- # Валидаторы > Валидаторы могут быть полезны для повторного использования логики проверки между различными типами полей. > > — [Django documentation](https://docs.djangoproject.com/en/stable/ref/validators/) В большинстве случаев, когда вы имеете дело с проверкой в DRF, вы просто полагаетесь на стандартную проверку полей, либо пишете явные методы проверки в сериализаторе или классах полей. Однако иногда вам захочется поместить логику валидации в компоненты многократного использования, чтобы ее можно было легко повторно использовать в кодовой базе. Этого можно достичь с помощью функций валидатора и классов валидатора. ## Валидация в DRF Валидация в сериализаторах DRF обрабатывается немного иначе, чем валидация в классе Django `ModelForm`. При использовании `ModelForm` валидация выполняется частично на форме, частично на экземпляре модели. При использовании DRF валидация выполняется полностью на классе сериализатора. Это выгодно по следующим причинам: * Обеспечивается правильное разделение обязанностей, что делает поведение вашего кода более очевидным. * Легко переключаться между использованием коротких классов `ModelSerializer` и явными классами `Serializer`. Любое поведение проверки, используемое для `ModelSerializer`, легко повторить. * Распечатка `repr` экземпляра сериализатора покажет вам, какие именно правила проверки он применяет. Нет никакого дополнительного скрытого поведения проверки, вызываемого на экземпляре модели. При использовании `ModelSerializer` все это обрабатывается автоматически. Если вы хотите перейти к использованию классов `Serializer`, то вам необходимо явно определить правила валидации. #### Пример В качестве примера того, как DRF использует явную валидацию, возьмем простой класс модели, в котором есть поле с ограничением уникальности. ```python class CustomerReportRecord(models.Model): time_raised = models.DateTimeField(default=timezone.now, editable=False) reference = models.CharField(unique=True, max_length=20) description = models.TextField() ``` Вот базовый `ModelSerializer`, который мы можем использовать для создания или обновления экземпляров `CustomerReportRecord`: ```python class CustomerReportSerializer(serializers.ModelSerializer): class Meta: model = CustomerReportRecord ``` Если мы откроем оболочку Django с помощью `manage.py shell`, то теперь мы можем ```python >>> from project.example.serializers import CustomerReportSerializer >>> serializer = CustomerReportSerializer() >>> print(repr(serializer)) CustomerReportSerializer(): id = IntegerField(label='ID', read_only=True) time_raised = DateTimeField(read_only=True) reference = CharField(max_length=20, validators=[UniqueValidator(queryset=CustomerReportRecord.objects.all())]) description = CharField(style={'type': 'textarea'}) ``` Интересным здесь является поле `reference`. Мы видим, что ограничение уникальности явно обеспечивается валидатором на поле сериализатора. Из-за этого более явного стиля DRF включает несколько классов валидаторов, которые отсутствуют в основном Django. Эти классы подробно описаны ниже. Валидаторы DRF, как и их аналоги в Django, реализуют метод `__eq__`, позволяющий сравнивать экземпляры на равенство. --- ## UniqueValidator Этот валидатор может быть использован для обеспечения ограничения `unique=True` на поля модели. Он принимает один обязательный аргумент и необязательный аргумент `messages`: * `queryset` _обязательно_ - Это набор запросов, в отношении которого должна быть обеспечена уникальность. * `message` - Сообщение об ошибке, которое должно быть использовано при неудачной проверке. * `lookup` - Поиск, используемый для нахождения существующего экземпляра с проверяемым значением. По умолчанию `'exact'`. Этот валидатор должен применяться к _полям сериализатора_, например, так: ```python from rest_framework.validators import UniqueValidator slug = SlugField( max_length=100, validators=[UniqueValidator(queryset=BlogPost.objects.all())] ) ``` ## UniqueTogetherValidator Этот валидатор можно использовать для наложения ограничений `unique_together` на экземпляры моделей. Он имеет два обязательных аргумента и один необязательный аргумент `messages`: * `queryset` _обязательно_ - Это набор запросов, по которому должна быть обеспечена уникальность. * `fields` _обязательно_ - Список или кортеж имен полей, которые должны составлять уникальный набор. Они должны существовать как поля в классе сериализатора. * `message` - Сообщение об ошибке, которое должно быть использовано при неудачной проверке. Валидатор должен применяться к _классам сериализатора_, например, так: ```python from rest_framework.validators import UniqueTogetherValidator class ExampleSerializer(serializers.Serializer): # ... class Meta: # ToDo items belong to a parent list, and have an ordering defined # by the 'position' field. No two items in a given list may share # the same position. validators = [ UniqueTogetherValidator( queryset=ToDoItem.objects.all(), fields=['list', 'position'] ) ] ``` --- **Примечание**: Класс `UniqueTogetherValidator` всегда накладывает неявное ограничение на то, что все поля, к которым он применяется, всегда рассматриваются как обязательные. Поля со значениями `default` являются исключением из этого правила, так как они всегда предоставляют значение, даже если оно опущено при вводе пользователем. --- ## UniqueForDateValidator ## UniqueForMonthValidator ## UniqueForYearValidator Эти валидаторы могут использоваться для наложения ограничений `unique_for_date`, `unique_for_month` и `unique_for_year` на экземпляры модели. Они принимают следующие аргументы: * `queryset` _обязательно_ - Это набор запросов, по которым будет проверяться уникальность. * `field` _обязательно_ - Имя поля, по которому будет проверяться уникальность в заданном диапазоне дат. Оно должно существовать как поле в классе сериализатора. * `date_field` _обязательно_ - Имя поля, которое будет использоваться для определения диапазона дат для ограничения уникальности. Оно должно существовать как поле в классе сериализатора. * `message` - Сообщение об ошибке, которое должно быть использовано при неудачной проверке. Валидатор должен применяться к _классам сериализатора_, например, так: ```python from rest_framework.validators import UniqueForYearValidator class ExampleSerializer(serializers.Serializer): # ... class Meta: # Blog posts should have a slug that is unique for the current year. validators = [ UniqueForYearValidator( queryset=BlogPostItem.objects.all(), field='slug', date_field='published' ) ] ``` Поле даты, которое используется для проверки, всегда должно присутствовать в классе сериализатора. Вы не можете просто положиться на класс модели `default=...`, потому что значение, используемое по умолчанию, будет сгенерировано только после выполнения валидации. Есть несколько стилей, которые вы можете использовать для этого, в зависимости от того, как вы хотите, чтобы ваш API вел себя. Если вы используете `ModelSerializer`, вы, вероятно, просто будете полагаться на значения по умолчанию, которые генерирует для вас DRF, но если вы используете `Serializer` или просто хотите более явного контроля, используйте один из стилей, продемонстрированных ниже. #### Использование с записываемым полем даты. Если вы хотите, чтобы поле даты было доступно для записи, единственное, что стоит отметить, это то, что вы должны убедиться, что оно всегда доступно во входных данных, либо задав аргумент `default`, либо установив `required=True`. ```python published = serializers.DateTimeField(required=True) ``` #### Использование с полем даты, доступным только для чтения. Если вы хотите, чтобы поле даты было видно, но не редактировалось пользователем, то установите `read_only=True` и дополнительно задайте аргумент `default=...`. ```python published = serializers.DateTimeField(read_only=True, default=timezone.now) ``` #### Использование со скрытым полем даты. Если вы хотите, чтобы поле даты было полностью скрыто от пользователя, используйте `HiddenField`. Этот тип поля не принимает пользовательский ввод, а вместо этого всегда возвращает значение по умолчанию в `validated_data` в сериализаторе. ```python published = serializers.HiddenField(default=timezone.now) ``` --- **Примечание**: Классы `UniqueForValidator` накладывают неявное ограничение на то, что поля, к которым они применяются, всегда рассматриваются как обязательные. Поля со значениями `default` являются исключением из этого правила, так как они всегда предоставляют значение, даже если оно опущено при вводе пользователем. --- **Примечание:** `HiddenField()` не появляется в сериализаторе `partial=True` (при выполнении запроса `PATCH`). --- # Расширенные значения полей по умолчанию Валидаторы, применяемые к нескольким полям в сериализаторе, иногда могут потребовать ввода поля, которое не должно предоставляться клиентом API, но которое _доступно_ в качестве входных данных для валидатора. Для этих целей используйте `HiddenField`. Это поле будет присутствовать в `validated_data`, но _не_ будет использоваться в выходном представлении сериализатора. --- **Примечание:** Использование поля `read_only=True` исключается из записываемых полей, поэтому оно не будет использовать аргумент `default=...`. Смотрите [3.8 объявление](https://www.django-rest-framework.org/community/3.8-announcement/#altered-the-behavior-of-read_only-plus-default-on-field). --- DRF включает в себя несколько параметров по умолчанию, которые могут быть полезны в данном контексте. #### CurrentUserDefault Класс по умолчанию, который можно использовать для представления текущего пользователя. Чтобы использовать его, `request` должен быть предоставлен как часть контекстного словаря при инстанцировании сериализатора. ```python owner = serializers.HiddenField( default=serializers.CurrentUserDefault() ) ``` #### CreateOnlyDefault Класс по умолчанию, который можно использовать _только для установки аргумента по умолчанию во время операций создания_. При обновлении поле опускается. Он принимает один аргумент, который является значением по умолчанию или вызываемым модулем, который должен использоваться при создании. ```python created_at = serializers.DateTimeField( default=serializers.CreateOnlyDefault(timezone.now) ) ``` --- # Ограничения валидаторов Есть несколько неоднозначных случаев, когда вам придется явно обрабатывать валидацию, а не полагаться на классы сериализаторов по умолчанию, которые генерирует `ModelSerializer`. В таких случаях вы можете отключить автоматически сгенерированные валидаторы, указав пустой список для атрибута сериализатора `Meta.validators`. ## Необязательные поля По умолчанию валидация `unique_together` принудительно требует, чтобы все поля были `required=True`. В некоторых случаях вы можете захотеть явно применить `required=False` к одному из полей, и тогда желаемое поведение валидации будет неоднозначным. В этом случае, как правило, необходимо исключить валидатор из класса сериализатора, а всю логику проверки написать явно либо в методе `.validate()`, либо в представлении. Например: ```python class BillingRecordSerializer(serializers.ModelSerializer): def validate(self, attrs): # Apply custom validation either here, or in the view. class Meta: fields = ['client', 'date', 'amount'] extra_kwargs = {'client': {'required': False}} validators = [] # Remove a default "unique together" constraint. ``` ## Обновление вложенных сериализаторов При применении обновления к существующему экземпляру валидаторы уникальности исключают текущий экземпляр из проверки уникальности. Текущий экземпляр доступен в контексте проверки уникальности, поскольку он существует как атрибут сериализатора, будучи изначально переданным с помощью `instance=...` при инстанцировании сериализатора. В случае операций обновления для _вложенных_ сериализаторов нет возможности применить это исключение, поскольку экземпляр недоступен. Опять же, вероятно, вам захочется явно удалить валидатор из класса сериализатора и написать код для ограничения валидации явно, в методе `.validate()` или в представлении. ## Отладка сложных случаев Если вы не уверены в том, какое именно поведение будет генерировать класс `ModelSerializer`, обычно полезно запустить `manage.py shell` и распечатать экземпляр сериализатора, чтобы вы могли проверить поля и валидаторы, которые он автоматически генерирует для вас. ```python >>> serializer = MyComplexModelSerializer() >>> print(serializer) class MyComplexModelSerializer: my_fields = ... ``` Также имейте в виду, что в сложных случаях часто бывает лучше явно определить свои классы сериализатора, а не полагаться на поведение `ModelSerializer` по умолчанию. Это потребует немного больше кода, но гарантирует, что результирующее поведение будет более прозрачным. --- # Написание пользовательских валидаторов Вы можете использовать любой из существующих валидаторов Django или написать свой собственный валидатор. ## Валидаторы, основанные на функциях Валидатором может быть любой вызываемый объект, который при неудаче вызывает ошибку `serializers.ValidationError`. ```python def even_number(value): if value % 2 != 0: raise serializers.ValidationError('This field must be an even number.') ``` #### Валидация на уровне поля Вы можете задать пользовательскую проверку на уровне полей, добавив методы `.validate_<имя_поля>` в подкласс `Serializer`. Это описано в [документации по сериализаторам](../api-guide/serializers.md#валидация-на-уровне-поля) ## На основе класса Чтобы написать валидатор на основе класса, используйте метод `__call__`. Валидаторы на основе классов полезны тем, что позволяют параметризировать и повторно использовать поведение. ```python class MultipleOf: def __init__(self, base): self.base = base def __call__(self, value): if value % self.base != 0: message = 'This field must be a multiple of %d.' % self.base raise serializers.ValidationError(message) ``` #### Доступ к контексту В некоторых сложных случаях вы можете захотеть, чтобы валидатору передавалось поле сериализатора, с которым он используется, в качестве дополнительного контекста. Вы можете сделать это, установив атрибут `requires_context = True` для класса валидатора. Тогда метод `__call__` будет вызван с полем `serializer_field` или `serializer` в качестве дополнительного аргумента. ```python class MultipleOf: requires_context = True def __call__(self, value, serializer_field): ... ``` ================================================ FILE: api-guide/versioning.md ================================================ # Версионирование > Версионирование интерфейса - это просто "вежливый" способ убить развернутых клиентов. > > - [Рой Филдинг](https://www.slideshare.net/evolve_conference/201308-fielding-evolve/31). Версионность API позволяет изменять поведение различных клиентов. DRF предусматривает несколько различных схем версионирования. Версионность определяется входящим запросом клиента и может быть основана либо на URL запроса, либо на заголовках запроса. Существует несколько правильных подходов к определению версий. [Неверсионированные системы также могут быть уместны](https://www.infoq.com/articles/roy-fielding-on-versioning), особенно если вы разрабатываете очень долгосрочные системы с множеством клиентов вне вашего контроля. ## Версионирование с помощью DRF Если версионность API включена, атрибут `request.version` будет содержать строку, соответствующую версии, запрошенной во входящем клиентском запросе. По умолчанию версионность не включена, и `request.version` всегда будет возвращать `None`. #### Различное поведение в зависимости от версии Как вы будете изменять поведение API, зависит от вас, но один из примеров, который обычно может понадобиться, - это переход на другой стиль сериализации в новой версии. Например: ```python def get_serializer_class(self): if self.request.version == 'v1': return AccountSerializerVersion1 return AccountSerializer ``` #### Обратные URL для версионных API Функция `reverse`, включенная в DRF, связана со схемой версионирования. Вам нужно убедиться, что вы включили текущий `request` в качестве именованного аргумента, как, например. ```python from rest_framework.reverse import reverse reverse('bookings-list', request=request) ``` Приведенная выше функция будет применять любые преобразования URL, соответствующие версии запроса. Например: * Если используется `NamespaceVersioning`, и версия API равна 'v1', то для поиска URL используется `'v1:bookings-list'`, что может привести к URL типа `http://example.org/v1/bookings/`. * Если используется `QueryParameterVersioning`, и версия API была '1.0', то возвращаемый URL может быть чем-то вроде `http://example.org/bookings/?version=1.0`. #### Версифицированные API и сериализаторы с гиперссылками При использовании стилей сериализации с гиперссылками вместе со схемой версионирования на основе URL обязательно включайте запрос в качестве контекста для сериализатора. ```python def get(self, request): queryset = Booking.objects.all() serializer = BookingsSerializer(queryset, many=True, context={'request': request}) return Response({'all_bookings': serializer.data}) ``` Это позволит всем возвращаемым URL-адресам включать соответствующие версии. ## Настройка схемы версионирования Схема версионирования определяется ключом настройки `DEFAULT_VERSIONING_CLASS`. ```python REST_FRAMEWORK = { 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning' } ``` Если оно не задано явно, значение для `DEFAULT_VERSIONING_CLASS` будет равно `None`. В этом случае атрибут `request.version` всегда будет возвращать `None`. Вы также можете установить схему версионирования для отдельного представления. Обычно вам не нужно этого делать, поскольку логичнее иметь единую схему версионирования, используемую глобально. Если это необходимо, используйте атрибут `versioning_class`. ```python class ProfileList(APIView): versioning_class = versioning.QueryParameterVersioning ``` #### Другие параметры версионирования Следующие ключи настроек также используются для управления версионированием: * `DEFAULT_VERSION`. Значение, которое должно использоваться для `request.version`, когда информация о версиях отсутствует. По умолчанию `None`. * `ALLOWED_VERSIONS`. Если задано, это значение ограничивает набор версий, которые могут быть возвращены схемой версионирования, и выдает ошибку, если предоставленная версия не входит в этот набор. Обратите внимание, что значение, используемое для параметра `DEFAULT_VERSION`, всегда считается частью набора `ALLOWED_VERSIONS` (если оно не равно `None`). По умолчанию `None`. * `VERSION_PARAM`. Строка, которая должна использоваться для любых параметров версионирования, например, в типе медиа или параметрах запроса URL. По умолчанию имеет значение `'version'`. Вы также можете установить свой класс версионности плюс эти три значения на основе каждого представления или каждого набора представлений, определив свою собственную схему версионности и используя переменные класса `default_version`, `allowed_versions` и `version_param`. Например, если вы хотите использовать `URLPathVersioning`: ```python from rest_framework.versioning import URLPathVersioning from rest_framework.views import APIView class ExampleVersioning(URLPathVersioning): default_version = ... allowed_versions = ... version_param = ... class ExampleView(APIVIew): versioning_class = ExampleVersioning ``` --- # API Reference ## AcceptHeaderVersioning Эта схема требует от клиента указать версию как часть типа медиа в заголовке `Accept`. Версия включается в качестве параметра медиатипа, дополняющего основной медиатип. Вот пример HTTP-запроса с использованием стиля версионирования заголовка accept. ```http GET /bookings/ HTTP/1.1 Host: example.com Accept: application/json; version=1.0 ``` В приведенном выше примере запроса атрибут `request.version` вернет строку `'1.0'`. Версионирование на основе принимаемых заголовков [обычно рассматривается](http://blog.steveklabnik.com/posts/2011-07-03-nobody-understands-rest-or-http#i_want_my_api_to_be_versioned) как [лучшая практика](https://github.com/interagent/http-api-design/blob/master/en/foundations/require-versioning-in-the-accepts-header.md), хотя в зависимости от требований клиента могут подойти и другие стили. #### Использование заголовков accept с медиатипами поставщиков Строго говоря, медиатип `json` не указан как [включающий дополнительные параметры](https://tools.ietf.org/html/rfc4627#section-6). Если вы создаете хорошо специфицированный публичный API, вы можете рассмотреть возможность использования [vendor media type](https://en.wikipedia.org/wiki/Internet_media_type#Vendor_tree). Для этого настройте свои рендереры на использование рендерера на основе JSON с пользовательским медиатипом: ```python class BookingsAPIRenderer(JSONRenderer): media_type = 'application/vnd.megacorp.bookings+json' ``` Теперь ваши клиентские запросы будут выглядеть следующим образом: ```http GET /bookings/ HTTP/1.1 Host: example.com Accept: application/vnd.megacorp.bookings+json; version=1.0 ``` ## URLPathVersioning Эта схема требует, чтобы клиент указывал версию как часть пути URL. ```http GET /v1/bookings/ HTTP/1.1 Host: example.com Accept: application/json ``` Ваш URL conf должен включать шаблон, соответствующий версии, с именованным аргументом `'version'`, чтобы эта информация была доступна схеме версионирования. ```python urlpatterns = [ re_path( r'^(?P(v1|v2))/bookings/$', bookings_list, name='bookings-list' ), re_path( r'^(?P(v1|v2))/bookings/(?P[0-9]+)/$', bookings_detail, name='bookings-detail' ) ] ``` ## NamespaceVersioning Для клиента эта схема аналогична `URLPathVersioning`. Единственное различие заключается в том, как она настраивается в вашем приложении Django, поскольку она использует интервалы между именами URL, вместо именованных аргументов URL. ```http GET /v1/something/ HTTP/1.1 Host: example.com Accept: application/json ``` При такой схеме атрибут `request.version` определяется на основе `namespace`, соответствующего пути входящего запроса. В следующем примере мы даем набору представлений два различных возможных префикса URL, каждый из которых относится к разным пространствам имен: ```python # bookings/urls.py urlpatterns = [ re_path(r'^$', bookings_list, name='bookings-list'), re_path(r'^(?P[0-9]+)/$', bookings_detail, name='bookings-detail') ] # urls.py urlpatterns = [ re_path(r'^v1/bookings/', include('bookings.urls', namespace='v1')), re_path(r'^v2/bookings/', include('bookings.urls', namespace='v2')) ] ``` И `URLPathVersioning`, и `NamespaceVersioning` разумны, если вам нужна простая схема версионирования. Подход `URLPathVersioning` может лучше подойти для небольших специальных проектов, а `NamespaceVersioning`, вероятно, проще в управлении для больших проектов. ## HostNameVersioning Схема версионности имени хоста требует от клиента указать запрашиваемую версию как часть имени хоста в URL. Например, ниже приведен HTTP-запрос к URL `http://v1.example.com/bookings/`: ```http GET /bookings/ HTTP/1.1 Host: v1.example.com Accept: application/json ``` По умолчанию эта реализация ожидает, что имя хоста будет соответствовать этому простому регулярному выражению: ```python ^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$ ``` Обратите внимание, что первая группа заключена в скобки, что указывает на то, что это совпадающая часть имени хоста. Схема `HostNameVersioning` может быть неудобна для использования в режиме отладки, так как обычно вы обращаетесь к необработанному IP-адресу, например, `127.0.0.1`. Существуют различные онлайн-уроки о том, как [получить доступ к localhost с пользовательским поддоменом](https://reinteractive.net/posts/199-developing-and-testing-rails-applications-with-subdomains), которые могут оказаться полезными в этом случае. Версионность на основе имени хоста может быть особенно полезна, если у вас есть требования направлять входящие запросы на разные серверы в зависимости от версии, поскольку вы можете настроить разные записи DNS для разных версий API. ## QueryParameterVersioning Эта схема представляет собой простой стиль, который включает версию в качестве параметра запроса в URL. Например: ```http GET /something/?version=0.1 HTTP/1.1 Host: example.com Accept: application/json ``` --- # Пользовательские схемы версионирования Для реализации пользовательской схемы версионирования, подкласс `BaseVersioning` и переопределите метод `.determine_version`. ## Пример В следующем примере используется пользовательский заголовок `X-API-Version` для определения запрашиваемой версии. ```python class XAPIVersionScheme(versioning.BaseVersioning): def determine_version(self, request, *args, **kwargs): return request.META.get('HTTP_X_API_VERSION', None) ``` Если ваша схема версионирования основана на URL запроса, вы также захотите изменить способ определения версионированных URL. Для этого вам следует переопределить метод `.reverse()` в классе. Примеры см. в исходном коде. ================================================ FILE: api-guide/views.md ================================================ # Представления, основанные на классах > Представления Django, основанные на классах, являются приятным отступлением от представлений старого стиля. > > - [Reinout van Rees](https://reinout.vanrees.org/weblog/2011/08/24/class-based-views-usage.html) DRF предоставляет класс `APIView`, который является подклассом класса Django `View`. Классы `APIView` отличаются от обычных классов `View` следующим образом: * Запросы, передаваемые методам обработчика, будут экземплярами `Request` DRF, а не экземплярами `HttpRequest` Django. * Методы обработчика могут возвращать `Response` DRF, а не `HttpResponse` Django. Представление будет управлять согласованием содержимого и установкой правильного рендерера в ответе. * Любые исключения `APIException` будут перехвачены и переведены в соответствующие ответы. * Входящие запросы будут аутентифицированы, и перед отправкой запроса в метод-обработчик будут выполняться соответствующие проверки разрешений и/или дросселирования. Использование класса `APIView` практически не отличается от использования обычного класса `View`. Как обычно, входящий запрос передается соответствующему методу-обработчику, такому как `.get()` или `.post()`. Кроме того, для класса может быть установлен ряд атрибутов, которые контролируют различные аспекты политики API. Например: ```python from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import authentication, permissions from django.contrib.auth.models import User class ListUsers(APIView): """ View to list all users in the system. * Requires token authentication. * Only admin users are able to access this view. """ authentication_classes = [authentication.TokenAuthentication] permission_classes = [permissions.IsAdminUser] def get(self, request, format=None): """ Return a list of all users. """ usernames = [user.username for user in User.objects.all()] return Response(usernames) ``` --- **Примечание**: Полные методы, атрибуты и отношения между `APIView`, `GenericAPIView`, различными `Mixins` и `Viewsets` DRF могут быть изначально сложными. В дополнение к документации, представленной здесь, ресурс [Classy Django REST Framework](http://www.cdrf.co) предоставляет просматриваемую ссылку с полными методами и атрибутами для каждого из представлений DRF, основанных на классах. --- ## Атрибуты политики API Следующие атрибуты управляют подключаемыми аспектами представлений API. ### .renderer_classes ### .parser_classes ### .authentication_classes ### .throttle_classes ### .permission_classes ### .content_negotiation_class ## Методы инстанцирования политики API Следующие методы используются DRF для инстанцирования различных подключаемых политик API. Как правило, вам не нужно переопределять эти методы. ### .get_renderers(self) ### .get_parsers(self) ### .get_authenticators(self) ### .get_throttles(self) ### .get_permissions(self) ### .get_content_negotiator(self) ### .get_exception_handler(self) ## Методы реализации политики API Перед отправкой в метод обработчика вызываются следующие методы. ### .check_permissions(self, request) ### .check_throttles(self, request) ### .perform_content_negotiation(self, request, force=False) ## Dispatch методы Следующие методы вызываются непосредственно методом `.dispatch()` представления. Они выполняют любые действия, которые должны произойти до или после вызова методов обработчика, таких как `.get()`, `.post()`, `put()`, `patch()` и `.delete()`. ### .initial(self, request, *args, **kwargs) Выполняет любые действия, которые должны произойти до вызова метода обработчика. Этот метод используется для обеспечения разрешений и дросселирования, а также для согласования содержимого. Обычно вам не нужно переопределять этот метод. ### .handle_exception(self, exc) Любое исключение, выброшенное методом обработчика, будет передано в этот метод, который либо возвращает экземпляр `Response`, либо повторно вызывает исключение. Реализация по умолчанию обрабатывает любой подкласс `rest_framework.exceptions.APIException`, а также исключения Django `Http404` и `PermissionDenied`, и возвращает соответствующий ответ об ошибке. Если вам нужно настроить ответы на ошибки, которые возвращает ваш API, вам следует подклассифицировать этот метод. ### .initialize_request(self, request, *args, **kwargs) Гарантирует, что объект запроса, передаваемый методу обработчика, является экземпляром `Request`, а не обычным Django `HttpRequest`. Обычно вам не нужно переопределять этот метод. ### .finalize_response(self, request, response, *args, **kwargs) Гарантирует, что любой объект `Response`, возвращенный из метода обработчика, будет преобразован в правильный тип содержимого, как определено в процессе согласования содержимого. Обычно вам не нужно переопределять этот метод. --- # Представления на основе функций > Говорить [что представления, основанные на классах] всегда являются лучшим решением - это ошибка. > > - [Nick Coghlan](http://www.boredomandlaziness.org/2012/05/djangos-cbvs-are-not-mistake-but.html) DRF также позволяет работать с обычными представлениями, основанными на функциях. Он предоставляет набор простых декораторов, которые оборачивают ваши представления на основе функций, чтобы они получали экземпляр `Request` (а не обычный Django `HttpRequest`) и позволяли им возвращать `Response` (а не Django `HttpResponse`), а также позволяют вам настраивать, как обрабатывается запрос. ## @api_view() **Сигнатура:** `@api_view(http_method_names=['GET'])`. Ядром этой функциональности является декоратор `api_view`, который принимает список методов HTTP, на которые должно отвечать ваше представление. Например, вот как можно написать очень простое представление, которое просто вручную возвращает некоторые данные: ```python from rest_framework.decorators import api_view from rest_framework.response import Response @api_view() def hello_world(request): return Response({"message": "Hello, world!"}) ``` Это представление будет использовать рендереры по умолчанию, парсеры, классы аутентификации и т.д., указанные в [settings](settings.md). По умолчанию принимаются только методы `GET`. Другие методы будут отвечать "405 Method Not Allowed". Чтобы изменить это поведение, укажите, какие методы разрешены представлению, например, так: ```python @api_view(['GET', 'POST']) def hello_world(request): if request.method == 'POST': return Response({"message": "Got some data!", "data": request.data}) return Response({"message": "Hello, world!"}) ``` ## Декораторы политики API Чтобы переопределить настройки по умолчанию, DRF предоставляет набор дополнительных декораторов, которые можно добавить к вашим представлениям. Они должны быть *после* (ниже) декоратора `@api_view`. Например, чтобы создать представление, которое использует [throttle](throttling.md) для обеспечения того, что оно может быть вызвано только один раз в день определенным пользователем, используйте декоратор `@throttle_classes`, передавая список классов throttle: ```python from rest_framework.decorators import api_view, throttle_classes from rest_framework.throttling import UserRateThrottle class OncePerDayUserThrottle(UserRateThrottle): rate = '1/day' @api_view(['GET']) @throttle_classes([OncePerDayUserThrottle]) def view(request): return Response({"message": "Hello for today! See you tomorrow!"}) ``` Эти декораторы соответствуют атрибутам, установленным на подклассах `APIView`, описанных выше. Доступными декораторами являются: * `@renderer_classes(...)` * `@parser_classes(...)` * `@authentication_classes(...)` * `@throttle_classes(...)` * `@permission_classes(...)` * `@content_negotiation_class(...)` * `@metadata_class(...)` * `@versioning_class(...)` Каждый из этих декораторов эквивалентен установке соответствующих [атрибутов политики API](#атрибуты-политики-api). Все декораторы принимают один аргумент. Те, которые заканчиваются на `_class`, ожидают один класс, а те, которые заканчиваются на `_classes`, ожидают список или кортеж классов. ## Декоратор схемы представления Чтобы переопределить генерацию схемы по умолчанию для представлений на основе функций, вы можете использовать декоратор `@schema`. Он должен располагаться *после* (ниже) декоратора `@api_view`. Например: ```python from rest_framework.decorators import api_view, schema from rest_framework.schemas import AutoSchema class CustomAutoSchema(AutoSchema): def get_link(self, path, method, base_url): # override view introspection here... @api_view(['GET']) @schema(CustomAutoSchema()) def view(request): return Response({"message": "Hello for today! See you tomorrow!"}) ``` Этот декоратор принимает экземпляр `AutoSchema`, экземпляр подкласса `AutoSchema` или экземпляр `ManualSchema`, как описано в документации [Schemas documentation](schemas.md). Вы можете передать `None`, чтобы исключить представление из генерации схемы. ```python @api_view(['GET']) @schema(None) def view(request): return Response({"message": "Will not appear in schema!"}) ``` ================================================ FILE: api-guide/viewsets.md ================================================ # ViewSets > После того, как маршрутизация определила, какой контроллер использовать для запроса, ваш контроллер отвечает за осмысление запроса и создание соответствующего вывода. > > — [Ruby on Rails Documentation](https://guides.rubyonrails.org/action_controller_overview.html) DRF позволяет объединить логику для набора связанных представлений в одном классе, называемом `ViewSet`. В других фреймворках вы также можете встретить концептуально схожие реализации с названиями типа "Ресурсы" или "Контроллеры". Класс `ViewSet` - это просто **тип представления на основе класса, который не предоставляет никаких обработчиков методов**, таких как `.get()` или `.post()`, а вместо этого предоставляет такие действия, как `.list()` и `.create()`. Обработчики методов для `ViewSet` привязываются к соответствующим действиям только в момент финализации представления с помощью метода `.as_view()`. Обычно вместо того, чтобы явно регистрировать представления в наборе представлений в urlconf, вы регистрируете набор представлений в классе маршрутизатора, который автоматически определяет urlconf для вас. ## Пример Давайте определим простой набор представлений, который можно использовать для получения списка всех пользователей в системе или конкретного пользователя. ```python from django.contrib.auth.models import User from django.shortcuts import get_object_or_404 from myapps.serializers import UserSerializer from rest_framework import viewsets from rest_framework.response import Response class UserViewSet(viewsets.ViewSet): """ A simple ViewSet for listing or retrieving users. """ def list(self, request): queryset = User.objects.all() serializer = UserSerializer(queryset, many=True) return Response(serializer.data) def retrieve(self, request, pk=None): queryset = User.objects.all() user = get_object_or_404(queryset, pk=pk) serializer = UserSerializer(user) return Response(serializer.data) ``` Если нужно, мы можем разделить этот набор представлений на два отдельных представления, как показано ниже: ```python user_list = UserViewSet.as_view({'get': 'list'}) user_detail = UserViewSet.as_view({'get': 'retrieve'}) ``` Обычно мы не делаем этого, а регистрируем набор представлений в маршрутизаторе и позволяем автоматически генерировать urlconf. ```python from myapp.views import UserViewSet from rest_framework.routers import DefaultRouter router = DefaultRouter() router.register(r'users', UserViewSet, basename='user') urlpatterns = router.urls ``` --- **Предупреждение**: Не используйте `.as_view()` с методами `@action`. Это обходит настройки маршрутизатора и может игнорировать настройки действий, такие как `permission_classes`. Используйте `DefaultRouter` для действий. --- Вместо того чтобы писать свои собственные наборы представлений, вы часто захотите использовать существующие базовые классы, которые предоставляют набор поведения по умолчанию. Например: ```python class UserViewSet(viewsets.ModelViewSet): """ A viewset for viewing and editing user instances. """ serializer_class = UserSerializer queryset = User.objects.all() ``` Есть два основных преимущества использования класса `ViewSet` вместо класса `View`. * Повторяющаяся логика может быть объединена в один класс. В приведенном выше примере нам нужно указать `queryset` только один раз, и он будет использоваться в нескольких представлениях. * Используя маршрутизаторы, нам больше не нужно самим создавать URL conf. Оба варианта имеют свои преимущества. Использование обычных представлений и URL-конфигураций является более явным и дает вам больше контроля. Наборы представлений полезны, если вы хотите быстро приступить к работе, или если у вас большой API и вы хотите обеспечить последовательную конфигурацию URL во всем. ## Действия ViewSet Маршрутизаторы по умолчанию, входящие в состав DRF, обеспечивают маршруты для стандартного набора действий в стиле create/retrieve/update/destroy, как показано ниже: ```python class UserViewSet(viewsets.ViewSet): """ Example empty viewset demonstrating the standard actions that will be handled by a router class. If you're using format suffixes, make sure to also include the `format=None` keyword argument for each action. """ def list(self, request): pass def create(self, request): pass def retrieve(self, request, pk=None): pass def update(self, request, pk=None): pass def partial_update(self, request, pk=None): pass def destroy(self, request, pk=None): pass ``` ## Интроспекция действий ViewSet Во время диспетчеризации для `ViewSet` доступны следующие атрибуты. * `basename` - основа, используемая для имен создаваемых URL. * `action` - имя текущего действия (например, `list`, `create`). * `detail` - булево значение, указывающее, настроено ли текущее действие на просмотр списка или деталей. * `suffix` - суффикс отображения для типа набора представлений - зеркально отражает атрибут `detail`. * `name` - отображаемое имя набора представлений. Этот аргумент является взаимоисключающим для `suffix`. * `description` - отображаемое описание для отдельного вида набора представлений. Вы можете использовать эти атрибуты для настройки поведения в зависимости от текущего действия. Например, вы можете ограничить права на все действия, кроме действия `list`, следующим образом: ```python def get_permissions(self): """ Instantiates and returns the list of permissions that this view requires. """ if self.action == 'list': permission_classes = [IsAuthenticated] else: permission_classes = [IsAdminUser] return [permission() for permission in permission_classes] ``` --- **Примечание**: атрибут `action` недоступен в методах `get_parsers`, `get_authenticators` и `get_content_negotiator`, так как он устанавливается _после_ их вызова в жизненном цикле фреймворка. Если вы переопределите один из этих методов и попытаетесь получить доступ к атрибуту `action` в них, вы получите ошибку `AttributeError`. --- ## Добавление дополнительных действий в маршрутизацию Если у вас есть специальные методы, которые должны быть маршрутизируемыми, вы можете пометить их как таковые с помощью декоратора `@action`. Как и обычные действия, дополнительные действия могут быть предназначены как для одного объекта, так и для целой коллекции. Чтобы указать это, установите аргумент `detail` в `True` или `False`. Маршрутизатор настроит свои шаблоны URL соответствующим образом. Например, `DefaultRouter` настроит подробные действия так, чтобы они содержали `pk` в своих шаблонах URL. Более полный пример дополнительных действий: ```python from django.contrib.auth.models import User from rest_framework import status, viewsets from rest_framework.decorators import action from rest_framework.response import Response from myapp.serializers import UserSerializer, PasswordSerializer class UserViewSet(viewsets.ModelViewSet): """ A viewset that provides the standard actions """ queryset = User.objects.all() serializer_class = UserSerializer @action(detail=True, methods=['post']) def set_password(self, request, pk=None): user = self.get_object() serializer = PasswordSerializer(data=request.data) if serializer.is_valid(): user.set_password(serializer.validated_data['password']) user.save() return Response({'status': 'password set'}) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @action(detail=False) def recent_users(self, request): recent_users = User.objects.all().order_by('-last_login') page = self.paginate_queryset(recent_users) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(recent_users, many=True) return Response(serializer.data) ``` Декоратор `action` по умолчанию направляет запросы `GET`, но может принимать и другие HTTP-методы, задав аргумент `methods`. Например: ```python @action(detail=True, methods=['post', 'delete']) def unset_password(self, request, pk=None): ... ``` Аргумент `methods` также поддерживает методы HTTP, определенные как [HTTPMethod](https://docs.python.org/3/library/http.html#http.HTTPMethod). Пример ниже идентичен приведенному выше: ```python from http import HTTPMethod @action(detail=True, methods=[HTTPMethod.POST, HTTPMethod.DELETE]) def unset_password(self, request, pk=None): ... ``` Декоратор позволяет переопределить любую конфигурацию на уровне набора представлений, такую как `permission_classes`, `serializer_class`, `filter_backends`...: ```python @action(detail=True, methods=['post'], permission_classes=[IsAdminOrIsSelf]) def set_password(self, request, pk=None): ... ``` Два новых действия будут доступны по адресам `^users/{pk}/set_password/$` и `^users/{pk}/unset_password/$`. Используйте параметры `url_path` и `url_name` для изменения сегмента URL и обратного имени URL действия. Чтобы просмотреть все дополнительные действия, вызовите метод `.get_extra_actions()`. ### Маршрутизация дополнительных методов HTTP для дополнительных действий Дополнительные действия могут сопоставлять дополнительные HTTP-методы с отдельными методами `ViewSet`. Например, описанные выше методы установки и снятия пароля могут быть объединены в один маршрут. Обратите внимание, что дополнительные сопоставления не принимают аргументов. ```python @action(detail=True, methods=["put"], name="Change Password") def password(self, request, pk=None): """Update the user's password.""" ... @password.mapping.delete def delete_password(self, request, pk=None): """Delete the user's password.""" ... ``` ## Получение URL-адреса действия Если вам нужно получить URL-адрес действия, используйте метод `.reverse_action()`. Это удобная обертка для `reverse()`, автоматически передающая объект `request` представления и дополняющая `url_name` атрибутом `.basename`. Обратите внимание, что `basename` предоставляется маршрутизатором во время регистрации `ViewSet`. Если вы не используете маршрутизатор, то вы должны предоставить аргумент `basename` методу `.as_view()`. Используя пример из предыдущего раздела: ```pycon >>> view.reverse_action("set-password", args=["1"]) 'http://localhost:8000/api/users/1/set_password' ``` В качестве альтернативы можно использовать атрибут `url_name`, установленный декоратором `@action`. ```pycon >>> view.reverse_action(view.set_password.url_name, args=["1"]) 'http://localhost:8000/api/users/1/set_password' ``` Аргумент `url_name` для `.reverse_action()` должен совпадать с тем же аргументом декоратора `@action`. Кроме того, этот метод можно использовать для отмены действий по умолчанию, таких как `list` и `create`. --- # API Reference ## ViewSet Класс `ViewSet` наследуется от `APIView`. Вы можете использовать любые стандартные атрибуты, такие как `permission_classes`, `authentication_classes`, чтобы управлять политикой API для набора представлений. Класс `ViewSet` не предоставляет никаких реализаций действий. Чтобы использовать класс `ViewSet`, вам нужно переопределить его и явно определить реализацию действий. ## GenericViewSet Класс `GenericViewSet` наследуется от `GenericAPIView` и предоставляет стандартный набор методов `get_object`, `get_queryset` и другие базовые поведения общих представлений, но по умолчанию не включает никаких действий. Чтобы использовать класс `GenericViewSet`, вы должны переопределить его и либо смешать необходимые классы mixin, либо явно определить реализацию действий. ## ModelViewSet Класс `ModelViewSet` наследуется от `GenericAPIView` и включает в себя реализации различных действий, смешивая поведение различных классов-миксинов. Класс `ModelViewSet` предоставляет следующие действия: `.list()`, `.retrieve()`, `.create()`, `.update()`, `.partial_update()` и `.destroy()`. #### Пример Поскольку `ModelViewSet` расширяет `GenericAPIView`, вам обычно нужно предоставить как минимум атрибуты `queryset` и `serializer_class`. Например: ```python class AccountViewSet(viewsets.ModelViewSet): """ A simple ViewSet for viewing and editing accounts. """ queryset = Account.objects.all() serializer_class = AccountSerializer permission_classes = [IsAccountAdminOrReadOnly] ``` Обратите внимание, что вы можете использовать любой из стандартных атрибутов или переопределений методов, предоставляемых `GenericAPIView`. Например, чтобы использовать `ViewSet`, который динамически определяет набор запросов, с которым он должен работать, вы можете сделать что-то вроде этого: ```python class AccountViewSet(viewsets.ModelViewSet): """ A simple ViewSet for viewing and editing the accounts associated with the user. """ serializer_class = AccountSerializer permission_classes = [IsAccountAdminOrReadOnly] def get_queryset(self): return self.request.user.accounts.all() ``` Однако обратите внимание, что, после удаления свойства `queryset` из вашего `ViewSet`, любой связанный с ним [router](routers.md) не сможет автоматически вывести базовое имя вашей Модели, поэтому вам придется указать именованный аргумент `basename` как часть вашей регистрации [router](routers.md). Также обратите внимание, что хотя этот класс по умолчанию предоставляет полный набор действий create/list/retrieve/update/destroy, вы можете ограничить доступные операции с помощью стандартных классов разрешений. ## ReadOnlyModelViewSet Класс `ReadOnlyModelViewSet` также наследуется от `GenericAPIView`. Как и `ModelViewSet`, он также включает реализации различных действий, но в отличие от `ModelViewSet` предоставляет только действия "только для чтения", `.list()` и `.retrieve()`. #### Пример Как и в случае с `ModelViewSet`, обычно вам нужно предоставить как минимум атрибуты `queryset` и `serializer_class`. Например: ```python class AccountViewSet(viewsets.ReadOnlyModelViewSet): """ A simple ViewSet for viewing accounts. """ queryset = Account.objects.all() serializer_class = AccountSerializer ``` Как и в случае с `ModelViewSet`, вы можете использовать любые стандартные атрибуты и переопределения методов, доступные для `GenericAPIView`. # Пользовательские базовые классы ViewSet Вам может понадобиться предоставить пользовательские классы `ViewSet`, которые не имеют полного набора действий `ModelViewSet` или настраивают поведение каким-либо другим способом. ## Пример Чтобы создать базовый класс набора представлений, обеспечивающий операции `create`, `list` и `retrieve`, наследуйте от `GenericViewSet` и добавьте необходимые действия: ```python from rest_framework import mixins, viewsets class CreateListRetrieveViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): """ A viewset that provides `retrieve`, `create`, and `list` actions. To use it, override the class and set the `.queryset` and `.serializer_class` attributes. """ pass ``` Создавая собственные базовые классы `ViewSet`, вы можете обеспечить общее поведение, которое может быть повторно использовано в нескольких наборах представлений в вашем API. ================================================ FILE: monitoring_config.toml ================================================ [repository] repo_url = 'https://github.com/encode/django-rest-framework.git' repo_branch = 'main' original_link = 'https://github.com/encode/django-rest-framework/tree/main/docs' paths = [ ['docs/api-guide/', '.reference/api-guide/'], ['docs/tutorial/', '.reference/tutorial/'], ['docs/coreapi/', '.reference/tutorial/'], ['docs/topics/', '.reference/topics/'], ['docs/index.md', '.reference/README.md'], ] cache_file = '.files_cache.json' ================================================ FILE: quickstart.md ================================================ # Быстрый старт Мы создадим простой API, который позволит администраторам просматривать и редактировать пользователей и группы в системе. ## Настройка проекта Создайте новый проект Django под названием `tutorial`, затем создайте новое приложение под названием `quickstart`. ```bash # Create the project directory mkdir tutorial cd tutorial # Create a virtual environment to isolate our package dependencies locally python3 -m venv env source env/bin/activate # On Windows use `env\Scripts\activate` # Install Django and Django REST framework into the virtual environment pip install djangorestframework # Set up a new project with a single application django-admin startproject tutorial . # Note the trailing '.' character cd tutorial django-admin startapp quickstart cd .. ``` Схема проекта должна выглядеть следующим образом: ```bash $ pwd /tutorial $ find . . ./tutorial ./tutorial/asgi.py ./tutorial/__init__.py ./tutorial/quickstart ./tutorial/quickstart/migrations ./tutorial/quickstart/migrations/__init__.py ./tutorial/quickstart/models.py ./tutorial/quickstart/__init__.py ./tutorial/quickstart/apps.py ./tutorial/quickstart/admin.py ./tutorial/quickstart/tests.py ./tutorial/quickstart/views.py ./tutorial/settings.py ./tutorial/urls.py ./tutorial/wsgi.py ./env ./env/... ./manage.py ``` Может показаться необычным, что приложение создано в каталоге проекта. Использование пространства имен проекта позволяет избежать конфликта имен с внешними модулями (эта тема выходит за рамки данного краткого руководства). Теперь синхронизируйте базу данных в первый раз: ```bash python manage.py migrate ``` Мы также создадим начального пользователя с именем `admin` и паролем. Мы будем аутентифицироваться под этим пользователем позже в нашем примере. ```bash python manage.py createsuperuser --username admin --email admin@example.com ``` После того как вы настроили базу данных и создали начального пользователя, откройте каталог приложения и приступайте к работе... ## Сериализаторы Сначала мы определим некоторые сериализаторы. Давайте создадим новый модуль `tutorial/quickstart/serializers.py`, который мы будем использовать для представления данных. ```python from django.contrib.auth.models import Group, User from rest_framework import serializers class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = User fields = ['url', 'username', 'email', 'groups'] class GroupSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Group fields = ['url', 'name'] ``` Обратите внимание, что в данном случае мы используем гиперссылки с помощью `HyperlinkedModelSerializer`. Вы также можете использовать первичный ключ и различные другие отношения, но гиперсвязь - это хороший дизайн RESTful. ## Представления Итак, нам нужно написать несколько представлений. Откройте `tutorial/quickstart/views.py` и начните печатать. ```python from django.contrib.auth.models import Group, User from rest_framework import permissions, viewsets from tutorial.quickstart.serializers import GroupSerializer, UserSerializer class UserViewSet(viewsets.ModelViewSet): """ API endpoint that allows users to be viewed or edited. """ queryset = User.objects.all().order_by('-date_joined') serializer_class = UserSerializer permission_classes = [permissions.IsAuthenticated] class GroupViewSet(viewsets.ModelViewSet): """ API endpoint that allows groups to be viewed or edited. """ queryset = Group.objects.all().order_by('name') serializer_class = GroupSerializer permission_classes = [permissions.IsAuthenticated] ``` Вместо того, чтобы писать несколько представлений, мы объединяем все общее поведение в классы под названием `ViewSets`. При необходимости мы можем легко разбить их на отдельные представления, но использование наборов представлений позволяет сохранить логику представления хорошо организованной, а также очень лаконичной. ## URL-адреса Итак, теперь давайте подключим URL-адреса API. Переходим к `tutorial/urls.py`... ```python from django.urls import include, path from rest_framework import routers from tutorial.quickstart import views router = routers.DefaultRouter() router.register(r'users', views.UserViewSet) router.register(r'groups', views.GroupViewSet) # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. urlpatterns = [ path('', include(router.urls)), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) ] ``` Поскольку мы используем наборы представлений, а не представления, мы можем автоматически генерировать URL conf для нашего API, просто зарегистрировав наборы представлений в классе маршрутизатора. Опять же, если нам нужен больший контроль над URL API, мы можем просто вернуться к использованию обычных представлений на основе классов и явного написания URL conf. Наконец, мы включаем стандартные представления входа и выхода для использования с Web-интерфейсом API. Это необязательно, но полезно, если ваш API требует аутентификации, а вы хотите использовать Web-интерфейс API. ## Пагинация Пагинация позволяет управлять количеством возвращаемых объектов на странице. Чтобы включить эту функцию, добавьте следующие строки в `tutorial/settings.py` ```python REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10 } ``` ## Настройки Добавьте `'rest_framework'` в `INSTALLED_APPS`. Модуль настроек будет находиться в файле `tutorial/settings.py`. ```python INSTALLED_APPS = [ ... 'rest_framework', ] ``` Хорошо, мы закончили. --- ## Тестирование нашего API Теперь мы готовы протестировать созданный нами API. Давайте запустим сервер из командной строки. ```bash python manage.py runserver ``` Теперь мы можем получить доступ к нашему API как из командной строки, так и с помощью таких инструментов, как `curl`... ```bash bash: curl -u admin -H 'Accept: application/json; indent=4' http://127.0.0.1:8000/users/ Enter host password for user 'admin': { "count": 1, "next": null, "previous": null, "results": [ { "url": "http://127.0.0.1:8000/users/1/", "username": "admin", "email": "admin@example.com", "groups": [] } ] } ``` Или с помощью [httpie](https://httpie.io/docs#installation), инструмента командной строки... ```bash bash: http -a admin http://127.0.0.1:8000/users/ http: password for admin@127.0.0.1:8000:: $HTTP/1.1 200 OK ... { "count": 1, "next": null, "previous": null, "results": [ { "email": "admin@example.com", "groups": [], "url": "http://127.0.0.1:8000/users/1/", "username": "admin" } ] } ``` Или непосредственно через браузер, перейдя по URL-адресу `http://127.0.0.1:8000/users/`... ![Изображение быстрого запуска](https://github.com/encode/django-rest-framework/raw/main/docs/img/quickstart.png) Если вы работаете через браузер, обязательно войдите в систему, используя элемент управления в правом верхнем углу. Отлично, это было легко! Если вы хотите получить более глубокое понимание того, как устроен DRF, перейдите к [учебнику](tutorial/1-serialization.md) или начните просматривать [руководство по API](api-guide/requests.md). ================================================ FILE: topics/ajax-csrf-cors.md ================================================ # Работа с AJAX, CSRF и CORS > "Внимательно изучите возможные уязвимости CSRF / XSRF на ваших собственных сайтах. Это худший вид уязвимостей - их очень легко использовать злоумышленникам, но не так просто интуитивно понять разработчикам программного обеспечения, по крайней мере, пока вас не укусит одна из них." > > - [Jeff Atwood](https://blog.codinghorror.com/preventing-csrf-and-xsrf-attacks/) ## Клиенты Javascript Если вы создаете JavaScript-клиент для взаимодействия с вашим Web API, вам нужно подумать, может ли клиент использовать ту же политику аутентификации, которая используется остальной частью сайта, а также определить, нужно ли вам использовать CSRF-токены или CORS-заголовки. AJAX-запросы, выполняемые в том же контексте, что и API, с которым они взаимодействуют, обычно используют `SessionAuthentication`. Это гарантирует, что после того, как пользователь вошел в систему, любые запросы AJAX могут быть аутентифицированы с помощью той же сеансовой аутентификации, которая используется для остальной части сайта. AJAX-запросы, которые выполняются на сайте, отличном от сайта API, с которым они взаимодействуют, обычно должны использовать схему аутентификации, не основанную на сеансах, например `TokenAuthentication`. ## Защита от CSRF [Защита от подделки межсайтовых запросов](https://owasp.org/www-community/attacks/csrf) - это механизм защиты от определенного типа атак, которые могут возникнуть, когда пользователь не вышел с веб-сайта и продолжает иметь действующую сессию. В этом случае вредоносный сайт может выполнить действия против целевого сайта в контексте вошедшей сессии. Чтобы защититься от такого рода атак, вам нужно сделать две вещи: 1. Убедитесь, что "безопасные" операции HTTP, такие как `GET`, `HEAD` и `OPTIONS`, не могут быть использованы для изменения состояния на стороне сервера. 2. Убедитесь, что для любых "небезопасных" операций HTTP, таких как `POST`, `PUT`, `PATCH` и `DELETE`, всегда требуется действительный токен CSRF. Если вы используете `SessionAuthentication`, вам необходимо включить действительные CSRF-токены для любых операций `POST`, `PUT`, `PATCH` или `DELETE`. Чтобы выполнять AJAX-запросы, необходимо включить CSRF-токен в HTTP-заголовок, как [описано в документации Django](https://docs.djangoproject.com/en/stable/howto/csrf/#using-csrf-protection-with-ajax). ## CORS [Cross-Origin Resource Sharing](https://www.w3.org/TR/cors/) - это механизм, позволяющий клиентам взаимодействовать с API, размещенными на другом домене. CORS работает, требуя от сервера включения определенного набора заголовков, которые позволяют браузеру определить, разрешены ли междоменные запросы и когда они должны быть разрешены. Лучший способ справиться с CORS в DRF - добавить необходимые заголовки ответа в middleware. Это гарантирует, что CORS поддерживается прозрачно, без необходимости изменять какое-либо поведение в ваших представлениях. [Adam Johnson](https://github.com/adamchainz) поддерживает пакет [django-cors-headers](https://github.com/adamchainz/django-cors-headers), который, как известно, корректно работает с API DRF. ================================================ FILE: topics/browsable-api.md ================================================ # Browsable API > Это глубоко ошибочный трюизм... что мы должны культивировать привычку думать о том, что мы делаем. Все обстоит с точностью до наоборот. Цивилизация развивается за счет увеличения числа важных операций, которые мы можем выполнять, не задумываясь о них. > > — [Альфред Норт Уайтхед](https://en.wikiquote.org/wiki/Alfred_North_Whitehead), Введение в математику (1911) API может означать интерфейс прикладного _программирования_(Application _Programming_ Interface), но люди тоже должны уметь читать API; кто-то должен заниматься программированием. DRF поддерживает генерацию удобного для человека HTML-вывода для каждого ресурса при запросе формата `HTML`. Эти страницы позволяют легко просматривать ресурсы, а также содержат формы для отправки данных на ресурсы с помощью `POST`, `PUT` и `DELETE`. ## URLs Если вы включите в вывод ресурсов полностью определенные URL, они будут "урлизованы" и сделаны кликабельными для удобства просмотра человеком. Для этого в пакет `rest_framework` включен помощник [`reverse`](../api-guide/reverse.md). ## Форматы По умолчанию API возвращает формат, указанный в заголовках, который в случае браузера является HTML. Формат может быть указан с помощью `?format=` в запросе, так что вы можете просмотреть необработанный JSON-ответ в браузере, добавив `?format=json` к URL. Существуют полезные расширения для просмотра JSON в [Firefox](https://addons.mozilla.org/en-US/firefox/addon/jsonview/) и [Chrome](https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc). ## Аутентификация Чтобы быстро добавить аутентификацию в Web-интерфейсе API, добавьте маршруты с именами `"login"` и `"logout"` в пространстве имен `"rest_framework"`. DRF предоставляет для этого маршруты по умолчанию, которые вы можете добавить в свой urlconf: ```python from django.urls import include, path urlpatterns = [ # ... path("api-auth/", include("rest_framework.urls", namespace="rest_framework")) ] ``` ## Настройка Web-интерфейс API построен с использованием [Twitter's Bootstrap](https://getbootstrap.com/) (v 3.4.1), что позволяет легко настроить внешний вид и функциональность. Чтобы настроить стиль по умолчанию, создайте шаблон `rest_framework/api.html`, который расширяет `rest_framework/base.html`. Например: **templates/rest_framework/api.html** ```html {% extends "rest_framework/base.html" %} ... # Override blocks with required customizations ``` ### Переопределение темы по умолчанию Чтобы заменить тему по умолчанию, добавьте блок `bootstrap_theme` в ваш `api.html` и вставьте `ссылку` на нужный css-файл темы Bootstrap. Это полностью заменит включенную тему. ```html {% block bootstrap_theme %} {% endblock %} ``` Подходящие готовые темы для замены доступны на сайте [Bootswatch](https://bootswatch.com/). Чтобы использовать любую из тем Bootswatch, просто скачайте файл `bootstrap.min.css` этой темы, добавьте его в свой проект и замените тему по умолчанию, как описано выше. Убедитесь, что версия Bootstrap новой темы совпадает с версией темы по умолчанию. Вы также можете изменить вариант навигационной панели, которая по умолчанию имеет значение `navbar-inverse`, с помощью блока `bootstrap_navbar_variant`. Пустой блок `{% block bootstrap_navbar_variant %}{% endblock %}` будет использовать оригинальный стиль навигационной панели Bootstrap. Полный пример: ```html {% extends "rest_framework/base.html" %} {% block bootstrap_theme %} {% endblock %} {% block bootstrap_navbar_variant %}{% endblock %} ``` Для более специфических CSS-настроек, чем просто переопределение темы bootstrap по умолчанию, вы можете переопределить блок `style`. --- ![Cerulean theme](https://github.com/encode/django-rest-framework/raw/main/docs/img/cerulean.png) *Скриншот темы "Cerulean"* --- ![Slate theme](https://github.com/encode/django-rest-framework/raw/main/docs/img/slate.png) *Скриншот темы "Slate"* --- ### Пакеты сторонних разработчиков для настройки Вы можете использовать сторонние пакеты для кастомизации, а не делать это самостоятельно. Вот 3 пакета для настройки API: * [drf-restwind](https://github.com/youzarsiph/drf-restwind) - Современное переосмысление REST-фреймворка Django использует TailwindCSS и DaisyUI для создания гибких и настраиваемых решений пользовательского интерфейса с минимальными усилиями по кодированию. * [drf-redesign](https://github.com/youzarsiph/drf-redesign) - Пакет для настройки API с помощью Bootstrap 5. Современный и элегантный дизайн, поддержка темного режима. * [drf-material](https://github.com/youzarsiph/drf-material) - Материальный дизайн для Django REST Framework. --- ![API Root](https://github.com/encode/django-rest-framework/raw/main/docs/img/drf-rw-api-root.png) ![List View](https://github.com/encode/django-rest-framework/raw/main/docs/img/drf-rw-list-view.png) ![Detail View](https://github.com/encode/django-rest-framework/raw/main/docs/img/drf-rw-detail-view.png) *Скриншоты drf-restwind* --- ![API Root](https://github.com/encode/django-rest-framework/raw/main/docs/img/drf-r-api-root.png) ![List View](https://github.com/encode/django-rest-framework/raw/main/docs/img/drf-r-list-view.png) ![Detail View](https://github.com/encode/django-rest-framework/raw/main/docs/img/drf-r-detail-view.png) *Скриншоты drf-redesign* --- ![API Root](https://github.com/encode/django-rest-framework/raw/main/docs/img/drf-m-api-root.png) ![List View](https://github.com/encode/django-rest-framework/raw/main/docs/img/drf-m-list-view.png) ![Detail View](https://github.com/encode/django-rest-framework/raw/main/docs/img/drf-m-detail-view.png) *Скриншоты drf-material* --- ### Блоки Все блоки, доступные в базовом шаблоне Web-интерфейса API, которые можно использовать в вашем `api.html`. * `body` - Весь html-тег ``. * `bodyclass` - Атрибут класса для тега ``, по умолчанию пустой. * `bootstrap_theme` - CSS для темы Bootstrap. * `bootstrap_navbar_variant` - CSS-класс для навигационной панели. * `branding` - Раздел брендирования navbar, см. [Bootstrap components](https://getbootstrap.com/2.3.2/components.html#navbar). * `breadcrumbs` - Ссылки, показывающие вложенность ресурсов, позволяющие пользователю вернуться к ним. Рекомендуется сохранять их, но их можно переопределить с помощью блока breadcrumbs. * `script` - Файлы JavaScript для страницы. * `style` - Таблицы стилей CSS для страницы. * `title` - Заголовок страницы. * `userlinks` - Список ссылок справа от заголовка, по умолчанию содержит ссылки на вход/выход. Чтобы добавить ссылки вместо замены, используйте `{{ block.super }}`, чтобы сохранить ссылки аутентификации. #### Компоненты Доступны все стандартные [Bootstrap-компоненты](https://getbootstrap.com/2.3.2/components.html). #### Всплывающие подсказки Web-интерфейс API использует компонент Bootstrap tooltips. Любой элемент с классом `js-tooltip` и атрибутом `title` имеет содержание заголовка, который будет отображать всплывающую подсказку при наведении. ### Шаблон для входа в систему Чтобы добавить брендинг и настроить внешний вид шаблона входа в систему, создайте шаблон `login.html` и добавьте его в свой проект, например: `templates/rest_framework/login.html`. Шаблон должен расширяться от `rest_framework/login_base.html`. Вы можете добавить название своего сайта или брендинг, включив блок брендинга: ```html {% extends "rest_framework/login_base.html" %} {% block branding %}

My Site Name

{% endblock %} ``` Вы также можете настроить стиль, добавив блок `bootstrap_theme` или `style`, аналогичный `api.html`. ### Расширенная настройка #### Контекст Контекст, доступный шаблону: * `allowed_methods` : Список методов, разрешенных ресурсу * `api_settings` : Настройки API * `available_formats` : Список форматов, разрешенных ресурсом * `breadcrumblist` : Список ссылок, следующих по цепочке вложенных ресурсов * `content` : Содержание ответа API * `description` : Описание ресурса, сгенерированное из его docstring * `name` : Название ресурса * `post_form` : Экземпляр формы для использования POST-формой (если разрешено) * `put_form` : Экземпляр формы для использования PUT-формой (если разрешено) * `display_edit_forms` : Булево значение, указывающее, будут ли отображаться формы POST, PUT и PATCH. * `request` : Объект запроса * `response` : Объект ответа * `version` : Версия Django REST Framework * `view` : Представление, обрабатывающее запрос * `FORMAT_PARAM` : Представление может принимать переопределение формата * `METHOD_PARAM` : Представление может принимать переопределение метода Вы можете переопределить метод `BrowsableAPIRenderer.get_context()`, чтобы настроить контекст, передаваемый в шаблон. #### Неиспользование файла base.html Для более продвинутой настройки, например, без основы Bootstrap или более тесной интеграции с остальным сайтом, вы можете просто убрать в `api.html` расширение `base.html`. Тогда содержание и возможности страницы будут зависеть только от вас. #### Обработка `ChoiceField` с большим количеством элементов. Когда в отношениях или `ChoiceField` слишком много элементов, рендеринг виджета, содержащего все варианты, может стать очень медленным, что приведет к плохой работе рендеринга Web-интерфейса API. Самый простой вариант в этом случае - заменить виджет select на стандартный текстовый виджет. Например: ```python author = serializers.HyperlinkedRelatedField( queryset=User.objects.all(), style={'base_template': 'input.html'} ) ``` #### Автозаполнение Альтернативным, но более сложным вариантом будет замена виджета ввода виджетом автозаполнения, который будет загружать и отображать только подмножество доступных вариантов по мере необходимости. Если вам нужно сделать это, вам придется потрудиться, чтобы создать собственный HTML-шаблон автозаполнения. Существует [множество пакетов для виджетов автозаполнения](https://www.djangopackages.com/grids/g/auto-complete/), например [django-autocomplete-light](https://github.com/yourlabs/django-autocomplete-light), к которым вы можете обратиться. Обратите внимание, что вы не сможете просто включить эти компоненты в качестве стандартных виджетов, а должны будете явно написать HTML-шаблон. Это связано с тем, что REST framework 3.0 больше не поддерживает именованный аргумент `widget`, так как теперь он использует шаблонизацию HTML. ================================================ FILE: topics/browser-enhancements.md ================================================ # Улучшения в браузере > "Есть два не вызывающих споров варианта использования перегруженного POST. Первое - это *имитация* унифицированного интерфейса HTTP для клиентов, таких как веб-браузеры, которые не поддерживают PUT или DELETE". > > - [RESTful Web Services](https://www.amazon.com/RESTful-Web-Services-Leonard-Richardson/dp/0596529260), Leonard Richardson & Sam Ruby. Для того чтобы Web-интерфейс API функционировал, есть несколько усовершенствований для браузеров, которые должны быть предоставлены REST-фреймворком. Начиная с версии 3.3.0 и далее они включаются с помощью javascript, используя библиотеку [ajax-form](https://github.com/tomchristie/ajax-form). ## PUT, DELETE и т.д. на основе браузера. Библиотека [AJAX form library](https://github.com/tomchristie/ajax-form) поддерживает браузерные методы `PUT`, `DELETE` и другие методы на HTML формах. После включения библиотеки используйте атрибут `data-method` на форме, как показано ниже: ```html ... ``` Обратите внимание, что до версии 3.3.0 эта поддержка осуществлялась на стороне сервера, а не на основе javascript. Стиль перегрузки методов (используемый в [Ruby on Rails](https://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work)) больше не поддерживается из-за некоторых проблем, возникающих при разборе запросов. ## Отправка контента вне формы с помощью браузера Отправка через браузер таких типов содержимого, как JSON, поддерживается [библиотекой форм AJAX](https://github.com/tomchristie/ajax-form), используя поля формы с атрибутами `data-override='content-type'` и `data-override='content'`. Например: ```html
``` Обратите внимание, что до версии 3.3.0 эта поддержка осуществлялась на стороне сервера, а не на основе javascript. ## Суффиксы формата на основе URL DRF может принимать параметры URL в стиле `?format=json`, что может быть полезным сокращением для определения типа содержимого, которое должно быть возвращено из представления. Это поведение контролируется с помощью параметра `URL_FORMAT_OVERRIDE`. ## Переопределение метода на основе заголовка HTTP До версии 3.3.0 поддерживался заголовок расширения `X-HTTP-Method-Override` для переопределения метода запроса. Это поведение больше не используется в ядре, но может быть добавлено при необходимости с помощью middleware. Например: ```python METHOD_OVERRIDE_HEADER = 'HTTP_X_HTTP_METHOD_OVERRIDE' class MethodOverrideMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): if request.method == 'POST' and METHOD_OVERRIDE_HEADER in request.META: request.method = request.META[METHOD_OVERRIDE_HEADER] return self.get_response(request) ``` ## URL based accept headers ## Заголовки accept на основе URL До версии 3.3.0 DRF включал встроенную поддержку параметров URL в стиле `?accept=application/json`, что позволяло переопределять заголовок `Accept`. После внедрения API согласования контента это поведение больше не включено в ядро, но может быть добавлено с помощью пользовательского класса согласования контента, если это необходимо. Например: ```python class AcceptQueryParamOverride() def get_accept_list(self, request): header = request.META.get('HTTP_ACCEPT', '*/*') header = request.query_params.get('_accept', header) return [token.strip() for token in header.split(',')] ``` ## Разве HTML5 не поддерживает формы PUT и DELETE? Нет. Одно время предполагалось, что HTML5 будет поддерживать формы `PUT` и `DELETE`, но позже это было [исключено из спецификации](https://www.w3.org/TR/html5-diff/#changes-2010-06-24). Остается [продолжающееся обсуждение](http://amundsen.com/examples/put-delete-forms/) добавления поддержки `PUT` и `DELETE`, а также того, как поддерживать типы содержимого, отличные от закодированных в форме данных. ================================================ FILE: topics/documenting-your-api.md ================================================ # Документирование вашего API > REST API должен тратить почти все свои усилия по описанию на определение типа(ов) носителей, используемых для представления ресурсов и управления состоянием приложения. > > - Рой Филдинг, [REST API должны быть гипертекстовыми](https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven) DRF предоставляет ряд различных вариантов документирования вашего API. Ниже приведен неполный список наиболее популярных из них. ## Пакеты сторонних разработчиков для поддержки OpenAPI ### drf-spectacular [drf-spectacular](https://github.com/tfranzel/drf-spectacular/) - это библиотека генерации схем [OpenAPI 3](https://openapis.org/) с явным акцентом на расширяемость, настраиваемость и генерацию клиентов. Это рекомендуемый способ генерации и представления схем OpenAPI. Библиотека стремится извлечь как можно больше информации о схеме, предоставляя при этом декораторы и расширения для легкой настройки. Имеется явная поддержка [swagger-codegen](https://swagger.io/), [SwaggerUI](https://swagger.io/tools/swagger-ui/) и [Redoc](https://github.com/Rebilly/ReDoc), i18n, версионность, аутентификация, полиморфизм (динамические запросы и ответы), параметры запроса/пути/заголовка, документация и многое другое. Несколько популярных плагинов для DRF также поддерживаются "из коробки". ### drf-yasg [drf-yasg](https://github.com/axnsan12/drf-yasg/) - это инструмент генерации [Swagger / OpenAPI 2](https://swagger.io/), реализованный без использования генерации схем, предоставляемой DRF. Его цель - реализовать как можно больше спецификации [OpenAPI 2](https://openapis.org/) - вложенные схемы, именованные модели, тела ответов, валидаторы enum/pattern/min/max, параметры формы и др. - и генерировать документы, пригодные для использования с помощью инструментов генерации кода, таких как `swagger-codegen`. Это также воплощается в очень полезном интерактивном средстве просмотра документации в виде `swagger-ui`: ![Скриншот - drf-yasg](https://github.com/encode/django-rest-framework/raw/main/docs/img/drf-yasg.png) --- ## Built-in OpenAPI schema generation (deprecated) ## Встроенная генерация схем OpenAPI (устаревшая) **Уведомление о сокращении: Встроенная в REST framework поддержка генерации схем OpenAPI устарела в пользу сторонних пакетов, которые могут предоставить эту функциональность вместо нее. В качестве замены мы рекомендуем использовать пакет [drf-spectacular](#drf-spectacular).**. Существует ряд пакетов, позволяющих генерировать HTML-страницы документации на основе схем OpenAPI. Два популярных варианта - [Swagger UI](https://swagger.io/tools/swagger-ui/) и [ReDoc](https://github.com/Rebilly/ReDoc). Оба требуют лишь указания местоположения статического файла схемы или динамической конечной точки `SchemaView`. ### Минимальный пример с Swagger UI Если предположить, что вы последовали примеру из документации по схемам для маршрутизации динамического `SchemaView`, минимальный шаблон Django для использования Swagger UI может быть таким: ```html Swagger
``` Сохраните его в папке templates под именем `swagger-ui.html`. Затем добавьте маршрут `TemplateView` в URL conf вашего проекта: ```python from django.views.generic import TemplateView urlpatterns = [ # ... # Route TemplateView to serve Swagger UI template. # * Provide `extra_context` with view name of `SchemaView`. path( "swagger-ui/", TemplateView.as_view( template_name="swagger-ui.html", extra_context={"schema_url": "openapi-schema"}, ), name="swagger-ui", ), ] ``` Расширенные возможности использования см. в [Swagger UI documentation](https://swagger.io/tools/swagger-ui/). ### Минимальный пример с ReDoc. Если предположить, что вы последовали примеру из документации по схемам для маршрутизации динамического `SchemaView`, минимальный шаблон Django для использования ReDoc может быть таким: ```html ReDoc ``` Сохраните его в папке шаблонов под именем `redoc.html`. Затем проложите маршрут `TemplateView` в URL conf вашего проекта: ```python from django.views.generic import TemplateView urlpatterns = [ # ... # Route TemplateView to serve the ReDoc template. # * Provide `extra_context` with view name of `SchemaView`. path( "redoc/", TemplateView.as_view( template_name="redoc.html", extra_context={"schema_url": "openapi-schema"} ), name="redoc", ), ] ``` Расширенное использование см. в [документации ReDoc](https://github.com/Rebilly/ReDoc). ## Самоописывающиеся API Web-интерфейс API, который предоставляет DRF, позволяет вашему API быть полностью самоописывающимся. Документация для каждой конечной точки API может быть предоставлена просто при посещении URL-адреса в браузере. ![Скриншот - API самоописания](https://github.com/encode/django-rest-framework/raw/main/docs/img/self-describing.png) --- #### Установка заголовка Заголовок, который используется в Web-интерфейсе API, генерируется из имени класса представления или имени функции. Любой суффикс `View` или `ViewSet` удаляется, а строка разделяется пробелами по прописным/строчным буквам или подчеркиваниям. Например, представление `UserListView`, будет называться `User List`, когда будет представлено в Web-интерфейсе API. При работе с наборами представлений к каждому сгенерированному представлению добавляется соответствующий суффикс. Например, набор представлений `UserViewSet` будет генерировать представления с именами `User List` и `User Instance`. #### Установка описания Описание в Web-интерфейсе API генерируется из docstring представления или набора представлений. Если установлена библиотека python `Markdown`, то в docstring можно использовать [синтаксис markdown](https://daringfireball.net/projects/markdown/syntax), который будет преобразован в HTML в Web-интерфейсе API. Например: ```python class AccountListView(views.APIView): """ Returns a list of all **active** accounts in the system. For more details on how accounts are activated please [see here][ref]. [ref]: http://example.com/activating-accounts """ ``` Обратите внимание, что при использовании наборов представлений базовая строка документа используется для всех создаваемых представлений. Чтобы предоставить описания для каждого представления, например, для представлений list и retrieve, используйте секции docstring, как описано в [Schemas as documentation](../api-guide/schemas.md). #### Метод `OPTIONS`. API DRF также поддерживают программно доступные описания, используя HTTP-метод `OPTIONS`. Представление отвечает на запрос `OPTIONS` с метаданными, включающими название, описание и различные типы медиа, которые оно принимает и на которые отвечает. При использовании общих представлений, любые запросы `OPTIONS` будут получать ответ с метаданными о любых доступных действиях `POST` или `PUT`, описывая, какие поля находятся в сериализаторе. Вы можете изменить поведение ответа на `OPTIONS` запросы, переопределив метод представления `options` и/или предоставив пользовательский класс Metadata. Например: ```python def options(self, request, *args, **kwargs): """ Don't include the view description in OPTIONS responses. """ meta = self.metadata_class() data = meta.determine_metadata(request, self) data.pop('description') return Response(data=data, status=status.HTTP_200_OK) ``` Более подробную информацию смотрите в [документации по метаданным](../api-guide/metadata.md). --- ## The hypermedia approach ## Гипермедийный подход Чтобы быть полностью RESTful, API должен представлять свои доступные действия в виде гипермедийных элементов управления в ответах, которые он отправляет. При таком подходе, вместо того, чтобы документировать доступные конечные точки API, описание концентрируется на *типах медиа*, которые используются. Доступные действия, которые могут быть предприняты на любом данном URL, не являются строго фиксированными, но вместо этого становятся доступными благодаря наличию элементов управления ссылками и формами в возвращаемом документе. Чтобы реализовать гипермедийный API, вам необходимо выбрать подходящий тип медиа для API и реализовать пользовательский рендерер и парсер для этого типа медиа. Раздел документации [REST, Hypermedia & HATEOAS](rest-hypermedia-hateoas.md) содержит указатели на справочную литературу, а также ссылки на различные форматы гипермедиа. ================================================ FILE: topics/html-and-forms.md ================================================ # HTML и формы DRF подходит для возврата как ответов в стиле API, так и обычных HTML-страниц. Кроме того, сериализаторы могут использоваться в качестве HTML-форм и отображаться в шаблонах. ## Рендеринг HTML Для возврата HTML-ответов вам нужно использовать либо `TemplateHTMLRenderer`, либо `StaticHTMLRenderer`. Класс `TemplateHTMLRenderer` ожидает, что ответ будет содержать словарь данных контекста, и создает HTML-страницу на основе шаблона, который должен быть указан либо в представлении, либо в ответе. Класс `StaticHTMLRender` ожидает, что ответ будет содержать строку предварительно отрендеренного HTML-содержимого. Поскольку поведение статических HTML-страниц обычно отличается от поведения ответов API, вам, вероятно, придется писать любые HTML-представления явно, а не полагаться на встроенные типовые представления. Вот пример представления, которое возвращает список экземпляров "Profile", отображенный в шаблоне HTML: **views.py**: ```python from my_project.example.models import Profile from rest_framework.renderers import TemplateHTMLRenderer from rest_framework.response import Response from rest_framework.views import APIView class ProfileList(APIView): renderer_classes = [TemplateHTMLRenderer] template_name = 'profile_list.html' def get(self, request): queryset = Profile.objects.all() return Response({'profiles': queryset}) ``` **profile_list.html**: ```html

Profiles

    {% for profile in profiles %}
  • {{ profile.name }}
  • {% endfor %}
``` ## Рендеринг форм Сериализаторы можно отображать в виде форм, используя тег шаблона `render_form` и включая экземпляр сериализатора в качестве контекста в шаблон. Следующее представление демонстрирует пример использования сериализатора в шаблоне для просмотра и обновления экземпляра модели: **views.py**: ```python from django.shortcuts import get_object_or_404 from my_project.example.models import Profile from rest_framework.renderers import TemplateHTMLRenderer from rest_framework.views import APIView class ProfileDetail(APIView): renderer_classes = [TemplateHTMLRenderer] template_name = 'profile_detail.html' def get(self, request, pk): profile = get_object_or_404(Profile, pk=pk) serializer = ProfileSerializer(profile) return Response({'serializer': serializer, 'profile': profile}) def post(self, request, pk): profile = get_object_or_404(Profile, pk=pk) serializer = ProfileSerializer(profile, data=request.data) if not serializer.is_valid(): return Response({'serializer': serializer, 'profile': profile}) serializer.save() return redirect('profile-list') ``` **profile_detail.html**: ```html {% load rest_framework %}

Profile - {{ profile.name }}

{% csrf_token %} {% render_form serializer %}
``` ### Использование пакетов шаблонов Тег `render_form` принимает необязательный аргумент `template_pack`, который указывает, какой каталог шаблонов должен использоваться для рендеринга формы и полей формы. DRF включает три встроенных пакета шаблонов, все они основаны на Bootstrap 3. Встроенные стили: `horizontal`, `vertical` и `inline`. По умолчанию используется стиль `horizontal`. Чтобы использовать любой из этих пакетов шаблонов, вам необходимо также включить CSS Bootstrap 3. Следующий HTML будет ссылаться на версию CSS Bootstrap 3, размещенную в CDN: ```html … ``` Сторонние пакеты могут включать альтернативные пакеты шаблонов, в которые входит каталог шаблонов, содержащий необходимые шаблоны форм и полей. Давайте рассмотрим, как визуализировать каждый из трех доступных пакетов шаблонов. В этих примерах мы будем использовать один класс сериализатора для представления формы "Вход в систему". ```python class LoginSerializer(serializers.Serializer): email = serializers.EmailField( max_length=100, style={'placeholder': 'Email', 'autofocus': True} ) password = serializers.CharField( max_length=100, style={'input_type': 'password', 'placeholder': 'Password'} ) remember_me = serializers.BooleanField() ``` --- #### `rest_framework/vertical`. Представляет ярлыки формы над соответствующими входами элементов управления, используя стандартный макет Bootstrap. *Это пакет шаблонов по умолчанию.* ```html {% load rest_framework %} ...
{% csrf_token %} {% render_form serializer template_pack='rest_framework/vertical' %}
``` ![Пример вертикальной формы](https://github.com/encode/django-rest-framework/raw/main/docs/img/vertical.png) --- #### `rest_framework/horizontal`. Представляет ярлыки и элементы управления рядом друг с другом, используя разделение колонок 2/10. *Это стиль формы, используемый в Web-интерфейсе API и администраторских рендерах.* ```html {% load rest_framework %} ...
{% csrf_token %} {% render_form serializer %}
``` ![Пример горизонтальной формы](https://github.com/encode/django-rest-framework/raw/main/docs/img/horizontal.png) --- #### `rest_framework/inline`. Компактный стиль формы, который представляет все элементы управления в линию. ```html {% load rest_framework %} ...
{% csrf_token %} {% render_form serializer template_pack='rest_framework/inline' %}
``` ![Пример инлайн-формы](https://github.com/encode/django-rest-framework/raw/main/docs/img/inline.png) ## Стили полей Поля сериализатора могут иметь свой стиль рендеринга, настроенный с помощью именованного аргумента `style`. Этот аргумент представляет собой словарь опций, которые управляют используемым шаблоном и макетом. Наиболее распространенным способом настройки стиля поля является использование именованного аргумента стиля `base_template`, чтобы выбрать, какой шаблон из пакета шаблонов следует использовать. Например, чтобы отобразить `CharField` как HTML textarea, а не как HTML input по умолчанию, вы должны использовать что-то вроде этого: ```python details = serializers.CharField( max_length=1000, style={'base_template': 'textarea.html'} ) ``` Если вы хотите, чтобы поле отображалось с использованием пользовательского шаблона, который *не является частью включенного пакета шаблонов*, вы можете использовать опцию стиля `template`, чтобы полностью указать имя шаблона: ```python details = serializers.CharField( max_length=1000, style={'template': 'my-field-templates/custom-input.html'} ) ``` Шаблоны полей также могут использовать дополнительные свойства стиля, в зависимости от их типа. Например, шаблон `textarea.html` также принимает свойство `rows`, которое можно использовать для изменения размера элемента управления. ```python details = serializers.CharField( max_length=1000, style={'base_template': 'textarea.html', 'rows': 10} ) ``` Полный список опций `base_template` и связанных с ними опций стиля приведен ниже. | base_template | Правильные типы полей | Дополнительные параметры стиля | | ---------------------- | -------------------------------------------------------- | ------------------------------------------------ | | input.html | Любое строковое, числовое или поле даты/времени | input_type, placeholder, hide_label, autofocus | | textarea.html | `CharField` | rows, placeholder, hide_label | | select.html | `ChoiceField` или типы реляционных полей | hide_label | | radio.html | `ChoiceField` или реляционные типы поля | inline, hide_label | | select_multiple.html | `MultipleChoiceField` или реляционные поля с `many=True` | hide_label | | checkbox_multiple.html | `MultipleChoiceField` или реляционные поля с `many=True` | inline, hide_label | | checkbox.html | `BooleanField` | hide_label | | fieldset.html | Вложенный сериализатор | hide_label | | list_fieldset.html | `ListField` или вложенный сериализатор с `many=True` | hide_label | ================================================ FILE: topics/internationalization.md ================================================ # Интернационализация > Поддержка интернационализации не является необязательной. Она должна быть основной функцией. > > - [Яннис Лейдель, выступая на Django Under the Hood, 2015](https://youtu.be/Wa0VfS2q94Y). DRF поставляется с переводимыми сообщениями об ошибках. Вы можете сделать так, чтобы они отображались на вашем языке, используя [стандартные механизмы перевода Django](https://docs.djangoproject.com/en/stable/topics/i18n/translation). Это позволит вам: - Выбрать язык по умолчанию, отличный от английского, используя стандартную настройку Django `LANGUAGE_CODE`. - Позволить клиентам самим выбирать язык, используя `LocaleMiddleware`, включенный в Django. Типичное использование для клиентов API - включение заголовка запроса `Accept-Language`. ## Включение интернационализированных API Вы можете изменить язык по умолчанию с помощью стандартной настройки Django `LANGUAGE_CODE`: ```python LANGUAGE_CODE = "es-es" ``` Вы можете включить языковые запросы на каждый запрос, добавив `LocaleMiddleware` в настройку `MIDDLEWARE`: ```python MIDDLEWARE = [ ... 'django.middleware.locale.LocaleMiddleware' ] ``` Когда интернационализация по каждому запросу включена, клиентские запросы будут учитывать заголовок `Accept-Language`, где это возможно. Например, давайте сделаем запрос для неподдерживаемого типа медиа: **Запрос** ```http GET /api/users HTTP/1.1 Accept: application/xml Accept-Language: es-es Host: example.org ``` **Ответ** ```http HTTP/1.0 406 NOT ACCEPTABLE {"detail": "No se ha podido satisfacer la solicitud de cabecera de Accept."} ``` DRF включает эти встроенные переводы как для стандартных случаев исключений, так и для ошибок валидации сериализатора. Обратите внимание, что переводы относятся только к самим строкам ошибок. Формат сообщений об ошибках и ключи имен полей останутся неизменными. Пример тела ответа `400 Bad Request` может выглядеть следующим образом: ```http {"detail": {"username": ["Esse campo deve ser único."]}} ``` Если вы хотите использовать разные строки для таких частей ответа, как `detail` и `non_field_errors`, вы можете изменить это поведение, используя [пользовательский обработчик исключений](../api-guide/exceptions.md#пользовательская-обработка-исключений). #### Указание набора поддерживаемых языков. По умолчанию поддерживаются все доступные языки. Если вы хотите поддерживать только часть доступных языков, используйте стандартную настройку Django `LANGUAGES`: ```python LANGUAGES = [ ('de', _('German')), ('en', _('English')), ] ``` ## Добавление новых переводов Переводы REST Framework управляются на GitHub. Вы можете добавить новые языки перевода или обновить существующие, следуя инструкциям в разделе [Вклад в REST Framework](https://www.django-rest-framework.org/community/contributing.md#development) и отправив запрос на извлечение. Иногда вам может понадобиться добавить строки перевода в ваш проект локально. Это может понадобиться, если: - Вы хотите использовать REST Framework на языке, который не поддерживается проектом. - Ваш проект включает пользовательские сообщения об ошибках, которые не входят в строки перевода DRF по умолчанию. #### Локальный перевод на новый язык Это руководство предполагает, что вы уже знакомы с тем, как перевести приложение Django. Если это не так, начните с чтения [Django's translation docs](https://docs.djangoproject.com/en/stable/topics/i18n/translation). Если вы делаете перевод на новый язык, вам нужно будет перевести существующие сообщения об ошибках DRF: 1. Создайте новую папку, в которой вы хотите хранить ресурсы интернационализации. Добавьте этот путь в настройку [`LOCALE_PATHS`](https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-LOCALE_PATHS). 2. Теперь создайте подпапку для языка, на который вы хотите перевести. Папка должна быть названа с использованием нотации [имя локали](https://docs.djangoproject.com/en/stable/topics/i18n/#term-locale-name). Например: `de`, `pt_BR`, `es_AR`. 3. Теперь скопируйте файл [base translations file](https://raw.githubusercontent.com/encode/django-rest-framework/main/rest_framework/locale/en_US/LC_MESSAGES/django.po) из исходного кода DRF в папку translations. 4. Отредактируйте только что скопированный файл `django.po`, переведя все сообщения об ошибках. 5. Запустите `manage.py compilemessages -l pt_BR`, чтобы сделать переводы доступными для использования Django. Вы должны увидеть сообщение типа `processing file django.po in <...>/locale/pt_BR/LC_MESSAGES`. 6. Перезапустите ваш сервер разработки, чтобы увидеть, что изменения вступили в силу. Если вы переводите только пользовательские сообщения об ошибках, которые существуют в кодовой базе вашего проекта, вам не нужно копировать исходный файл DRF `django.po` в папку `LOCALE_PATHS`, а можно просто запустить стандартный процесс Django `makemessages`. ## Как определяется язык Если вы хотите разрешить языковые предпочтения для каждого запроса, вам нужно включить `django.middleware.locale.LocaleMiddleware` в настройку `MIDDLEWARE`. Более подробную информацию о том, как определяется предпочтение языка, вы можете найти в [документации Django](https://docs.djangoproject.com/en/stable/topics/i18n/translation/#how-django-discovers-language-preference). Для справки, метод следующий: 1. Во-первых, он ищет префикс языка в запрашиваемом URL. 2. Если это не удается, он ищет ключ `LANGUAGE_SESSION_KEY` в текущей сессии пользователя. 3. Если это не удается, выполняется поиск cookie. 4. Если это не удается, просматривается HTTP-заголовок `Accept-Language`. 5. Если это не удается, используется глобальная настройка `LANGUAGE_CODE`. Для клиентов API наиболее подходящим из них обычно является использование заголовка `Accept-Language`; сеансы и cookies будут недоступны, если не используется аутентификация сеанса, и вообще лучше предпочесть заголовок `Accept-Language` для клиентов API, а не использовать языковые префиксы URL. ================================================ FILE: topics/rest-hypermedia-hateoas.md ================================================ # REST, гипермедиа и HATEOAS > Вы продолжаете использовать это слово "REST". Я не думаю, что оно означает то, что вы думаете, что оно означает. > > - Майк Амундсен, [REST fest 2012 keynote](https://vimeo.com/channels/restfest/49503453). Во-первых, отказ от ответственности. Название "Django REST framework" было принято еще в начале 2011 года и было выбрано просто для того, чтобы разработчики могли легко найти проект. Во всей документации мы стараемся использовать более простую и технически корректную терминологию "Web API". Если вы серьезно относитесь к разработке Hypermedia API, вам следует обратиться к ресурсам за пределами этой документации, чтобы помочь в выборе дизайна. Следующее относится к категории "обязательного чтения". - Диссертация Роя Филдинга - [Архитектурные стили и проектирование сетевых архитектур программного обеспечения](https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm). - Запись в блоге Роя Филдинга "[REST API должны быть гипертекстовыми](https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven)". - Leonard Richardson & Mike Amundsen's [RESTful Web APIs](http://restfulwebapis.org/). - Mike Amundsen's [Building Hypermedia APIs with HTML5 and Node](https://www.amazon.com/Building-Hypermedia-APIs-HTML5-Node/dp/1449306578). - Steve Klabnik's [Designing Hypermedia APIs](http://designinghypermediaapis.com/). - [Модель зрелости Ричардсона](https://martinfowler.com/articles/richardsonMaturityModel.html). For a more thorough background, check out Klabnik's [Hypermedia API reading list](http://blog.steveklabnik.com/posts/2012-02-27-hypermedia-api-reading-list). Для получения более подробной информации ознакомьтесь со списком [Hypermedia API reading list](http://blog.steveklabnik.com/posts/2012-02-27-hypermedia-api-reading-list) Клабника. ## Создание гипермедийных API с помощью DRF DRF - это агностический инструментарий Web API. Он помогает ориентироваться в создании хорошо связанных API и облегчает разработку соответствующих типов носителей, но не обеспечивает строгого соблюдения какого-либо определенного стиля оформления. ## Что предоставляетсобой DRF. Само собой разумеется, что DRF позволяет создавать гипермедийные API. Web-интерфейс API, который он предлагает, построен на HTML - гипермедийном языке Интернета. DRF также включает [serialization](../api-guide/serializers.md) и [parser](../api-guide/parsers.md)/[renderer](../api-guide/renderers.md), которые облегчают создание соответствующих типов медиа, [гиперсвязанные отношения](../api-guide/fields.md) для создания хорошо связанных систем, и отличная поддержка [согласования контента](../api-guide/content-negotiation.md). ## Чего не предоставляет DRF. Чего DRF не делает, так это не дает вам машиночитаемых гипермедийных форматов, таких как [HAL](http://stateless.co/hal_specification.html), [Collection+JSON](http://www.amundsen.com/media-types/collection/), [JSON API](http://jsonapi.org/) или HTML [microformats](http://microformats.org/wiki/Main_Page) по умолчанию, или возможности автоматически создавать API в стиле HATEOAS, которые включают гипермедийные описания форм и семантически маркированные гиперссылки. Это потребует принятия решений о дизайне API, которые должны оставаться за пределами сферы применения фреймворка. ================================================ FILE: topics/writable-nested-serializers.md ================================================ # Вложенные сериализаторы с возможностью записи > Для экономии HTTP-запросов может быть удобно отправлять вместе с запросом сопутствующие документы. > > - [Спецификация JSON API для Ember Data](http://jsonapi.org/format/#url-based-json-api). Хотя плоские структуры данных служат для правильного разграничения отдельных сущностей в вашем сервисе, бывают случаи, когда более целесообразно или удобно использовать вложенные структуры данных. С вложенными структурами данных достаточно легко работать, если они доступны только для чтения - просто вложите классы сериализатора, и все готово. Однако при использовании вложенных сериализаторов с возможностью записи есть еще несколько тонкостей, связанных с зависимостями между различными экземплярами модели и необходимостью сохранения или удаления нескольких экземпляров одним действием. ## Структуры данных "один ко многим" *Пример вложенного сериализатора **только для чтения**. Здесь нет ничего сложного.* ```python class ToDoItemSerializer(serializers.ModelSerializer): class Meta: model = ToDoItem fields = ['text', 'is_completed'] class ToDoListSerializer(serializers.ModelSerializer): items = ToDoItemSerializer(many=True, read_only=True) class Meta: model = ToDoList fields = ['title', 'items'] ``` Некоторые примеры вывода нашего сериализатора. ```python { 'title': 'Leaving party preparations', 'items': [ {'text': 'Compile playlist', 'is_completed': True}, {'text': 'Send invites', 'is_completed': False}, {'text': 'Clean house', 'is_completed': False} ] } ``` Давайте рассмотрим обновление нашей вложенной структуры данных "один ко многим". ### Ошибки валидации ### Добавление и удаление элементов ### Выполнение PATCH-запросов ================================================ FILE: topics.md ================================================ # Статьи * [AJAX, CSRF & CORS](topics/ajax-csrf-cors.md) * [The Browsable API](topics/browsable-api.md) * [Улучшения в браузере](topics/browser-enhancements.md) * [Документирование вашего API](topics/documenting-your-api.md) * [HTML и формы](topics/html-and-forms.md) * [Интернационализация](topics/internationalization.md) * [REST, гипермедиа и HATEOAS](topics/rest-hypermedia-hateoas.md) * [Вложенные сериализаторы с возможностью записи](topics/writable-nested-serializers.md) ================================================ FILE: tutorial/1-serialization.md ================================================ # Урок 1: Сериализация ## Введение В этом уроке мы рассмотрим создание простого Web API с подсветкой кода для фрагментов кода. Попутно будут представлены различные компоненты, составляющие DRF, и вы получите полное представление о том, как все это сочетается друг с другом. Учебник довольно подробный, поэтому перед началом работы вам, вероятно, стоит взять печенье и выпить чашку любимого напитка. Если вам нужен лишь краткий обзор, лучше обратиться к документации [quickstart](../quickstart.md). --- **Примечание**: Код для этого руководства доступен в репозитории [encode/rest-framework-tutorial](https://github.com/encode/rest-framework-tutorial) на GitHub. Не стесняйтесь клонировать репозиторий и посмотреть код в действии. --- ## Настройка новой среды Прежде чем делать что-либо еще, мы создадим новую виртуальную среду, используя [venv](https://docs.python.org/3/library/venv.html). Это позволит убедиться, что наша конфигурация пакетов будет изолирована от других проектов, над которыми мы работаем. ```bash python3 -m venv env source env/bin/activate ``` Теперь, когда мы находимся в виртуальной среде, мы можем установить наши зависимости. ```bash pip install django pip install djangorestframework pip install pygments # We'll be using this for the code highlighting ``` **Примечание:** Чтобы выйти из виртуальной среды в любое время, просто введите `deactivate`. Для получения дополнительной информации смотрите [документацию venv](https://docs.python.org/3/library/venv.html). ## Начало работы Итак, мы готовы приступить к кодированию. Чтобы начать, давайте создадим новый проект для работы. ```bash cd ~ django-admin startproject tutorial cd tutorial ``` После этого мы можем создать приложение, которое мы будем использовать для создания простого Web API. ```bash python manage.py startapp snippets ``` Нам нужно добавить наше новое приложение `snippets` и приложение `rest_framework` в `INSTALLED_APPS`. Давайте отредактируем файл `tutorial/settings.py`: ```python INSTALLED_APPS = [ ... 'rest_framework', 'snippets', ] ``` Хорошо, мы готовы к работе. ## Создание модели для работы Для целей этого руководства мы начнем с создания простой модели `Snippet`, которая используется для хранения фрагментов кода. Перейдите к редактированию файла `snippets/models.py`. Примечание: Хорошая практика программирования включает комментарии. Хотя вы найдете их в нашей версии этого учебного кода в репозитории, здесь мы их опустили, чтобы сосредоточиться на самом коде. ```python from django.db import models from pygments.lexers import get_all_lexers from pygments.styles import get_all_styles LEXERS = [item for item in get_all_lexers() if item[1]] LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS]) STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()]) class Snippet(models.Model): created = models.DateTimeField(auto_now_add=True) title = models.CharField(max_length=100, blank=True, default='') code = models.TextField() linenos = models.BooleanField(default=False) language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100) style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100) class Meta: ordering = ['created'] ``` Нам также потребуется создать начальную миграцию для нашей модели сниппетов и впервые синхронизировать базу данных. ```bash python manage.py makemigrations snippets python manage.py migrate snippets ``` ## Создание класса Serializer Первое, что нам нужно для начала работы над нашим Web API, это обеспечить способ сериализации и десериализации экземпляров сниппетов в такие форматы, как `json`. Мы можем сделать это, объявив сериализаторы, которые работают очень похоже на формы Django. Создайте файл в каталоге `snippets` с именем `serializers.py` и добавьте следующее. ```python from rest_framework import serializers from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES class SnippetSerializer(serializers.Serializer): id = serializers.IntegerField(read_only=True) title = serializers.CharField(required=False, allow_blank=True, max_length=100) code = serializers.CharField(style={'base_template': 'textarea.html'}) linenos = serializers.BooleanField(required=False) language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python') style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly') def create(self, validated_data): """ Create and return a new `Snippet` instance, given the validated data. """ return Snippet.objects.create(**validated_data) def update(self, instance, validated_data): """ Update and return an existing `Snippet` instance, given the validated data. """ instance.title = validated_data.get('title', instance.title) instance.code = validated_data.get('code', instance.code) instance.linenos = validated_data.get('linenos', instance.linenos) instance.language = validated_data.get('language', instance.language) instance.style = validated_data.get('style', instance.style) instance.save() return instance ``` Первая часть класса сериализатора определяет поля, которые сериализуются/десериализуются. Методы `create()` и `update()` определяют, как создаются или изменяются полноценные экземпляры при вызове `serializer.save()`. Класс сериализатора очень похож на класс Django `Form` и включает аналогичные флаги проверки различных полей, такие как `required`, `max_length` и `default`. Флаги полей также могут управлять тем, как сериализатор должен отображаться в определенных обстоятельствах, например, при рендеринге в HTML. Флаг `{'base_template': 'textarea.html'}` выше эквивалентен использованию `widget=widgets.Textarea` в классе Django `Form`. Это особенно полезно для управления отображением Web-интерфейса API, как мы увидим далее в учебнике. Мы также можем сэкономить время, используя класс `ModelSerializer`, как мы увидим позже, но пока мы сохраним определение нашего сериализатора явным. ## Работа с сериализаторами Прежде чем двигаться дальше, мы ознакомимся с использованием нашего нового класса Serializer. Давайте зайдем в оболочку Django. ```bash python manage.py shell ``` Хорошо, когда мы разобрались с несколькими импортами, давайте создадим пару фрагментов кода для работы. ```pycon >>> from snippets.models import Snippet >>> from snippets.serializers import SnippetSerializer >>> from rest_framework.renderers import JSONRenderer >>> from rest_framework.parsers import JSONParser >>> snippet = Snippet(code='foo = "bar"\n') >>> snippet.save() >>> snippet = Snippet(code='print("hello, world")\n') >>> snippet.save() ``` Теперь у нас есть несколько экземпляров фрагментов, с которыми можно поиграть. Давайте посмотрим на сериализацию одного из этих экземпляров. ```pycon >>> serializer = SnippetSerializer(snippet) >>> serializer.data {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'} ``` На данном этапе мы перевели экземпляр модели в собственные типы данных Python. Для завершения процесса сериализации мы преобразуем данные в `json`. ```pycon >>> content = JSONRenderer().render(serializer.data) >>> content b'{'id':2,'title':'','code':'print(\\"hello, world\\")\\n','linenos':false,'language':'python','style':'friendly'}' ``` Десериализация аналогична. Сначала мы разбираем поток на собственные типы данных Python... ```pycon >>> import io >>> stream = io.BytesIO(content) >>> data = JSONParser().parse(stream) ``` ...затем мы восстанавливаем эти собственные типы данных в полностью заполненный экземпляр объекта. ```pycon >>> serializer = SnippetSerializer(data=data) >>> serializer.is_valid() True >>> serializer.validated_data {'title': '', 'code': 'print("hello, world")', 'linenos': False, 'language': 'python', 'style': 'friendly'} >>> serializer.save() ``` Обратите внимание, насколько API похож на работу с формами. Сходство должно стать еще более очевидным, когда мы начнем писать представления, использующие наш сериализатор. Мы также можем сериализовать наборы запросов вместо экземпляров моделей. Для этого мы просто добавим флаг `many=True` в аргументы сериализатора. ```pycon >>> serializer = SnippetSerializer(Snippet.objects.all(), many=True) >>> serializer.data [{'id': 1, 'title': '', 'code': 'foo = "bar"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}, {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}, {'id': 3, 'title': '', 'code': 'print("hello, world")', 'linenos': False, 'language': 'python', 'style': 'friendly'}] ``` ## Использование сериализаторов моделей Наш класс `SnippetSerializer` повторяет много информации, которая также содержится в модели `Snippet`. Было бы неплохо, если бы мы могли сделать наш код более лаконичным. Подобно тому, как Django предоставляет классы `Form` и `ModelForm`, DRF включает классы `Serializer` и `ModelSerializer`. Давайте рассмотрим рефакторинг нашего сериализатора с помощью класса `ModelSerializer`. Снова откройте файл `snippets/serializers.py` и замените класс `SnippetSerializer` на следующий. ```python class SnippetSerializer(serializers.ModelSerializer): class Meta: model = Snippet fields = ['id', 'title', 'code', 'linenos', 'language', 'style'] ``` Одним из приятных свойств сериализаторов является то, что вы можете просмотреть все поля экземпляра сериализатора, распечатав его представление. Откройте оболочку Django с помощью команды `python manage.py shell`, затем попробуйте выполнить следующее: ```pycon >>> from snippets.serializers import SnippetSerializer >>> serializer = SnippetSerializer() >>> print(repr(serializer)) SnippetSerializer(): id = IntegerField(label='ID', read_only=True) title = CharField(allow_blank=True, max_length=100, required=False) code = CharField(style={'base_template': 'textarea.html'}) linenos = BooleanField(required=False) language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')... style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')... ``` Важно помнить, что классы `ModelSerializer` не делают ничего особенно волшебного, они просто являются синтаксическим сахаром для создания классов сериализаторов: * Автоматически определяемый набор полей. * Простые реализации по умолчанию для методов `create()` и `update()`. ## Написание обычных представлений Django с использованием нашего сериализатора Давайте посмотрим, как мы можем написать несколько представлений API, используя наш новый класс Serializer. На данный момент мы не будем использовать другие возможности фреймворка REST, мы просто напишем представления как обычные представления Django. Отредактируйте файл `snippets/views.py` и добавьте следующее. ```python from django.http import HttpResponse, JsonResponse from django.views.decorators.csrf import csrf_exempt from rest_framework.parsers import JSONParser from snippets.models import Snippet from snippets.serializers import SnippetSerializer ``` Корнем нашего API будет представление, которое поддерживает вывод списка всех существующих сниппетов или создание нового сниппета. ```python @csrf_exempt def snippet_list(request): """ List all code snippets, or create a new snippet. """ if request.method == 'GET': snippets = Snippet.objects.all() serializer = SnippetSerializer(snippets, many=True) return JsonResponse(serializer.data, safe=False) elif request.method == 'POST': data = JSONParser().parse(request) serializer = SnippetSerializer(data=data) if serializer.is_valid(): serializer.save() return JsonResponse(serializer.data, status=201) return JsonResponse(serializer.errors, status=400) ``` Обратите внимание, что, поскольку мы хотим иметь возможность делать POST запросы к этому представлению от клиентов, у которых не будет CSRF токена, мы должны пометить представление как `csrf_exempt`. Это не то, что вы обычно хотите сделать, и представления DRF на самом деле используют более разумное поведение, чем это, но для наших целей сейчас это подойдет. Нам также понадобится представление, которое соответствует отдельному фрагменту и может быть использовано для получения, обновления или удаления фрагмента. ```python @csrf_exempt def snippet_detail(request, pk): """ Retrieve, update or delete a code snippet. """ try: snippet = Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: return HttpResponse(status=404) if request.method == 'GET': serializer = SnippetSerializer(snippet) return JsonResponse(serializer.data) elif request.method == 'PUT': data = JSONParser().parse(request) serializer = SnippetSerializer(snippet, data=data) if serializer.is_valid(): serializer.save() return JsonResponse(serializer.data) return JsonResponse(serializer.errors, status=400) elif request.method == 'DELETE': snippet.delete() return HttpResponse(status=204) ``` Наконец, нам нужно подключить эти представления. Создайте файл `snippets/urls.py`: ```python from django.urls import path from snippets import views urlpatterns = [ path('snippets/', views.snippet_list), path('snippets//', views.snippet_detail), ] ``` Нам также нужно настроить корневой urlconf в файле `tutorial/urls.py`, чтобы включить в нее URL нашего приложения-фрагмента. ```python from django.urls import path, include urlpatterns = [ path('', include('snippets.urls')), ] ``` Стоит отметить, что есть несколько крайних случаев, которые мы не обрабатываем должным образом в настоящее время. Если мы отправим неверно сформированный `json`, или если запрос будет сделан с методом, который представление не обрабатывает, то мы получим ответ 500 "ошибка сервера". Тем не менее это пока сойдет. ## Тестирование нашей первой попытки создания Web API Теперь мы можем запустить отладочный сервер, который будет обслуживать наши фрагменты. Выйти из оболочки... ```pycon >>> quit() ``` ...и запустите сервер разработки Django. ```bash python manage.py runserver Validating models... 0 errors found Django version 5.0, using settings 'tutorial.settings' Starting Development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. ``` В другом окне терминала мы можем протестировать сервер. Мы можем протестировать наш API, используя [curl](https://curl.haxx.se/) или [httpie](https://github.com/httpie/httpie#installation). Httpie - это удобный http-клиент, написанный на Python. Давайте установим его. Вы можете установить httpie с помощью pip: ```bash pip install httpie ``` Наконец, мы можем получить список всех сниппетов: ```bash http GET http://127.0.0.1:8000/snippets/ --unsorted HTTP/1.1 200 OK ... [ { "id": 1, "title": "", "code": "foo = \"bar\"\n", "linenos": false, "language": "python", "style": "friendly" }, { "id": 2, "title": "", "code": "print(\"hello, world\")\n", "linenos": false, "language": "python", "style": "friendly" }, { "id": 3, "title": "", "code": "print(\"hello, world\")", "linenos": false, "language": "python", "style": "friendly" } ] ``` Или мы можем получить конкретный фрагмент, обратившись к нему по id: ```bash http GET http://127.0.0.1:8000/snippets/2/ --unsorted HTTP/1.1 200 OK ... { "id": 2, "title": "", "code": "print(\"hello, world\")\n", "linenos": false, "language": "python", "style": "friendly" } ``` Аналогично, вы можете получить тот же json, посетив эти URL-адреса в веб-браузере. ## Где мы сейчас Пока что у нас все в порядке, у нас есть API сериализации, который очень похож на Django Forms API, и несколько обычных представлений Django. Наши представления API на данный момент не делают ничего особенного, кроме как обслуживают `json` ответы, и есть несколько крайних случаев обработки ошибок, которые мы хотели бы убрать, но это функционирующий Web API. В [части 2 учебника](2-requests-and-responses.md) мы посмотрим, как можно начать улучшать ситуацию. ================================================ FILE: tutorial/2-requests-and-responses.md ================================================ # Учебник 2: Запросы и ответы Начиная с этого момента мы действительно начнем освещать суть DRF. Давайте представим несколько основных строительных блоков. ## Объекты запроса DRF вводит объект `Request`, который расширяет обычный `HttpRequest` и обеспечивает более гибкий разбор запроса. Основной функциональностью объекта `Request` является атрибут `request.data`, который аналогичен `request.POST`, но более полезен для работы с Web API. ```python request.POST # Only handles form data. Only works for 'POST' method. request.data # Handles arbitrary data. Works for 'POST', 'PUT' and 'PATCH' methods. ``` ## Объекты ответа DRF также вводит объект `Response`, который является типом `TemplateResponse`, который принимает неотрендерреное содержимое и использует согласование содержимого для определения правильного типа содержимого для возврата клиенту. ```python return Response(data) # Renders to content type as requested by the client. ``` ## Коды состояния Использование числовых кодов состояния HTTP в представлениях не всегда удобно для чтения, и легко не заметить, если вы ошиблись с кодом ошибки. DRF предоставляет более явные идентификаторы для каждого кода состояния, такие как `HTTP_400_BAD_REQUEST` в модуле `status`. Хорошая идея - использовать их повсеместно, а не использовать числовые идентификаторы. ## Оборачивание представлений API DRF предоставляет две обертки, которые можно использовать для написания представлений API. 1. Декоратор `@api_view` для работы с представлениями, основанными на функциях. 2. Класс `APIView` для работы с представлениями на основе классов. Эти обертки предоставляют несколько функциональных возможностей, таких как обеспечение получения экземпляров `Request` в вашем представлении и добавление контекста к объектам `Response`, чтобы можно было выполнить согласование содержимого. Обертки также обеспечивают такое поведение, как возврат ответов `405 Method Not Allowed`, когда это необходимо, и обработку любых исключений `ParseError`, возникающих при доступе к `request.data` с неправильно сформированным вводом. ## Собираем все вместе Итак, давайте начнем использовать эти новые компоненты, чтобы немного отрефакторить наши представления. ```python from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response from snippets.models import Snippet from snippets.serializers import SnippetSerializer @api_view(['GET', 'POST']) def snippet_list(request): """ List all code snippets, or create a new snippet. """ if request.method == 'GET': snippets = Snippet.objects.all() serializer = SnippetSerializer(snippets, many=True) return Response(serializer.data) elif request.method == 'POST': serializer = SnippetSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) ``` Наше представление экземпляра улучшилось по сравнению с предыдущим примером. Оно немного лаконичнее, и код теперь очень похож на тот, который мы использовали при работе с Forms API. Мы также используем именованные коды состояния, что делает значения ответов более очевидными. Вот представление для отдельного фрагмента в модуле `views.py`. ```python @api_view(['GET', 'PUT', 'DELETE']) def snippet_detail(request, pk): """ Retrieve, update or delete a code snippet. """ try: snippet = Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) if request.method == 'GET': serializer = SnippetSerializer(snippet) return Response(serializer.data) elif request.method == 'PUT': serializer = SnippetSerializer(snippet, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == 'DELETE': snippet.delete() return Response(status=status.HTTP_204_NO_CONTENT) ``` Все это должно казаться очень знакомым - это не сильно отличается от работы с обычными представлениями Django. Обратите внимание, что мы больше не привязываем наши запросы или ответы к определенному типу содержимого. `request.data` может обрабатывать входящие запросы `json`, но может обрабатывать и другие форматы. Точно так же мы возвращаем объекты ответа с данными, но позволяем DRF преобразовать ответ в нужный нам тип содержимого. ## Добавление необязательных суффиксов формата к нашим URL-адресам Чтобы воспользоваться тем, что наши ответы больше не привязаны к одному типу содержимого, давайте добавим поддержку суффиксов формата в наши конечные точки API. Использование суффиксов формата дает нам URL, которые явно ссылаются на определенный формат, и означает, что наш API сможет обрабатывать такие URL, как [](http://example.com/api/items/4.json). Начните с добавления именованного аргумента `format` к обоим представлениям, как показано ниже. ```python def snippet_list(request, format=None): ``` и ```python def snippet_detail(request, pk, format=None): ``` Теперь немного обновите файл `snippets/urls.py`, чтобы добавить набор `format_suffix_patterns` в дополнение к существующим URL. ```python from django.urls import path from rest_framework.urlpatterns import format_suffix_patterns from snippets import views urlpatterns = [ path('snippets/', views.snippet_list), path('snippets//', views.snippet_detail), ] urlpatterns = format_suffix_patterns(urlpatterns) ``` Нам необязательно добавлять эти дополнительные шаблоны url, но это дает нам простой и чистый способ ссылаться на определенный формат. ## Как это выглядит? Перейдите к тестированию API из командной строки, как мы это делали в [учебнике часть 1](1-serialization.md). Все работает примерно так же, хотя мы получили более удобную обработку ошибок при отправке некорректных запросов. Мы можем получить список всех сниппетов, как и раньше. ```bash http http://127.0.0.1:8000/snippets/ HTTP/1.1 200 OK ... [ { "id": 1, "title": "", "code": "foo = \"bar\"\n", "linenos": false, "language": "python", "style": "friendly" }, { "id": 2, "title": "", "code": "print(\"hello, world\")\n", "linenos": false, "language": "python", "style": "friendly" } ] ``` Мы можем контролировать формат ответа, который мы получаем, либо используя заголовок `Accept`: ```bash http http://127.0.0.1:8000/snippets/ Accept:application/json # Request JSON http http://127.0.0.1:8000/snippets/ Accept:text/html # Request HTML ``` Либо путем добавления суффикса формата: ```bash http http://127.0.0.1:8000/snippets.json # JSON suffix http http://127.0.0.1:8000/snippets.api # Browsable API suffix ``` Аналогично, мы можем контролировать формат отправляемого запроса, используя заголовок `Content-Type`. ```bash # POST using form data http --form POST http://127.0.0.1:8000/snippets/ code="print(123)" { "id": 3, "title": "", "code": "print(123)", "linenos": false, "language": "python", "style": "friendly" } # POST using JSON http --json POST http://127.0.0.1:8000/snippets/ code="print(456)" { "id": 4, "title": "", "code": "print(456)", "linenos": false, "language": "python", "style": "friendly" } ``` Если вы добавите переключатель `--debug` к вышеуказанным запросам `http`, вы сможете увидеть тип запроса в заголовках запросов. Теперь откройте API в веб-браузере, посетив [](http://127.0.0.1:8000/snippets/). ### Возможность просмотра Поскольку API выбирает тип содержимого ответа на основе запроса клиента, он по умолчанию возвращает представление ресурса в формате HTML, когда ресурс запрашивается веб-браузером. Это позволяет API возвращать полностью доступное для веб-браузера представление HTML. Наличие browsable API - это огромный выигрыш в удобстве использования, он значительно упрощает разработку и использование вашего API. Это также значительно снижает барьер для входа в систему для других разработчиков, желающих ознакомиться с вашим API и работать с ним. Дополнительную информацию о функции browsable API и ее настройке см. в теме [browsable api](../topics/browsable-api.md). ## Что дальше? В [уроке 3](3-class-based-views.md) мы начнем использовать представления на основе классов и увидим, как общие представления уменьшают количество кода, который нам нужно писать. ================================================ FILE: tutorial/3-class-based-views.md ================================================ # Урок 3: Представления на основе классов Мы также можем писать наши представления API, используя представления на основе классов, а не на основе функций. Как мы увидим, это мощный паттерн, который позволяет нам повторно использовать общую функциональность и помогает нам сохранить наш код [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). ## Переписывание нашего API с использованием представлений на основе классов Мы начнем с того, что перепишем корневое представление как представление на основе классов. Все, что для этого нужно, это немного подправить `views.py`. ```python from snippets.models import Snippet from snippets.serializers import SnippetSerializer from django.http import Http404 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status class SnippetList(APIView): """ List all snippets, or create a new snippet. """ def get(self, request, format=None): snippets = Snippet.objects.all() serializer = SnippetSerializer(snippets, many=True) return Response(serializer.data) def post(self, request, format=None): serializer = SnippetSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) ``` Пока все хорошо. Это выглядит довольно похоже на предыдущий случай, но мы получили лучшее разделение между различными HTTP-методами. Нам также потребуется обновить представление экземпляра в `views.py`. ```python class SnippetDetail(APIView): """ Retrieve, update or delete a snippet instance. """ def get_object(self, pk): try: return Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: raise Http404 def get(self, request, pk, format=None): snippet = self.get_object(pk) serializer = SnippetSerializer(snippet) return Response(serializer.data) def put(self, request, pk, format=None): snippet = self.get_object(pk) serializer = SnippetSerializer(snippet, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def delete(self, request, pk, format=None): snippet = self.get_object(pk) snippet.delete() return Response(status=status.HTTP_204_NO_CONTENT) ``` Выглядит неплохо. Опять же, сейчас это все еще очень похоже на представление на основе функций. Нам также придется немного отрефакторить наш `snippets/urls.py` теперь, когда мы используем представления на основе классов. ```python from django.urls import path from rest_framework.urlpatterns import format_suffix_patterns from snippets import views urlpatterns = [ path('snippets/', views.SnippetList.as_view()), path('snippets//', views.SnippetDetail.as_view()), ] urlpatterns = format_suffix_patterns(urlpatterns) ``` Хорошо, мы закончили. Если вы запустите сервер разработки, все должно работать как прежде. ## Использование миксинов Одним из главных преимуществ использования представлений на основе классов является то, что они позволяют нам легко комбинировать многократно используемые фрагменты поведения. Операции create/retrieve/update/delete, которые мы использовали до сих пор, будут довольно похожими для всех создаваемых нами представлений API, основанных на моделях. Эти части общего поведения реализованы в классах-миксинах DRF. Давайте рассмотрим, как мы можем компоновать представления с помощью классов миксинов. Вот наш модуль `views.py`. ```python from snippets.models import Snippet from snippets.serializers import SnippetSerializer from rest_framework import mixins from rest_framework import generics class SnippetList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) ``` Сейчас мы рассмотрим, что именно здесь происходит. Мы создаем наше представление, используя `GenericAPIView`, и добавляем `ListModelMixin` и `CreateModelMixin`. Базовый класс обеспечивает основную функциональность, а классы-миксины предоставляют действия `.list()` и `.create()`. Затем мы явно привязываем методы `get` и `post` к соответствующим действиям. Пока все достаточно просто. ```python class SnippetDetail( mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView ): queryset = Snippet.objects.all() serializer_class = SnippetSerializer def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs) ``` Довольно похоже. Мы снова используем класс `GenericAPIView` для обеспечения основной функциональности, и добавляем миксины для обеспечения действий `.retrieve()`, `.update()` и `.destroy()`. ## Использование общих представлений на основе классов Используя классы-миксины, мы переписали представления, чтобы использовать немного меньше кода, чем раньше, но мы можем пойти еще на один шаг дальше. DRF предоставляет набор уже скомбинированных общих представлений, которые мы можем использовать, чтобы еще больше сократить наш модуль `views.py`. ```python from snippets.models import Snippet from snippets.serializers import SnippetSerializer from rest_framework import generics class SnippetList(generics.ListCreateAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer ``` Ух ты, как лаконично. Мы получили огромное количество функционала бесплатно, и наш код выглядит как хороший, чистый, идиоматический Django. Далее мы перейдем к [уроку 4](4-authentication-and-permissions.md), где мы рассмотрим, как мы можем работать с аутентификацией и разрешениями для нашего API. ================================================ FILE: tutorial/4-authentication-and-permissions.md ================================================ # Урок 4: Аутентификация и разрешения В настоящее время наш API не имеет ограничений на то, кто может редактировать или удалять фрагменты кода. Мы хотели бы иметь более продвинутое поведение, чтобы быть уверенными в том, что: * Фрагменты кода всегда связаны с создателем. * Создавать сниппеты могут только авторизованные пользователи. * Только создатель сниппета может обновлять или удалять его. * Неаутентифицированные запросы должны иметь полный доступ только для чтения. ## Добавление информации в нашу модель Мы собираемся внести несколько изменений в наш класс модели `Snippet`. Во-первых, добавим пару полей. Одно из этих полей будет использоваться для представления пользователя, создавшего фрагмент кода. Другое поле будет использоваться для хранения выделенного HTML-представления кода. Добавьте следующие два поля к модели `Snippet` в `models.py`. ```python owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE) highlighted = models.TextField() ``` Нам также нужно убедиться, что при сохранении модели мы заполним выделенное поле, используя библиотеку подсветки кода `pygments`. Нам понадобятся дополнительные импорты: ```python from pygments.lexers import get_lexer_by_name from pygments.formatters.html import HtmlFormatter from pygments import highlight ``` И теперь мы можем добавить метод `.save()` в наш класс модели: ```python def save(self, *args, **kwargs): """ Use the `pygments` library to create a highlighted HTML representation of the code snippet. """ lexer = get_lexer_by_name(self.language) linenos = 'table' if self.linenos else False options = {'title': self.title} if self.title else {} formatter = HtmlFormatter(style=self.style, linenos=linenos, full=True, **options) self.highlighted = highlight(self.code, lexer, formatter) super().save(*args, **kwargs) ``` Когда все будет готово, нам нужно будет обновить таблицы нашей базы данных. Обычно для этого мы создаем миграцию базы данных, но для целей данного руководства давайте просто удалим базу данных и начнем все сначала. ```bash rm -f db.sqlite3 rm -r snippets/migrations python manage.py makemigrations snippets python manage.py migrate ``` Возможно, вы также захотите создать несколько разных пользователей, чтобы использовать их для тестирования API. Быстрее всего это можно сделать с помощью команды `createsuperuser`. ```bash python manage.py createsuperuser ``` ## Добавление конечных точек для наших моделей User Теперь, когда у нас есть несколько пользователей для работы, нам лучше добавить представления этих пользователей в наш API. Создать новый сериализатор очень просто. В `serializers.py` добавьте: ```python from django.contrib.auth.models import User class UserSerializer(serializers.ModelSerializer): snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all()) class Meta: model = User fields = ['id', 'username', 'snippets'] ``` Поскольку `'snippets'` является *обратным* отношением к модели `User`, оно не будет включено по умолчанию при использовании класса `ModelSerializer`, поэтому нам нужно добавить явное поле для него. Мы также добавим пару представлений в `views.py`. Мы хотим использовать представления только для чтения для пользовательских представлений, поэтому мы будем использовать представления `ListAPIView` и `RetrieveAPIView`, основанные на общих классах. ```python from django.contrib.auth.models import User class UserList(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer class UserDetail(generics.RetrieveAPIView): queryset = User.objects.all() serializer_class = UserSerializer ``` Не забудьте также импортировать класс `UserSerializer`. ```python from snippets.serializers import UserSerializer ``` Наконец, нам нужно добавить эти представления в API, ссылаясь на них из URL conf. Добавьте следующее к шаблонам в `snippets/urls.py`. ```python path('users/', views.UserList.as_view()), path('users//', views.UserDetail.as_view()), ``` ## Связывание сниппетов с пользователями Сейчас, если мы создаем сниппет кода, нет возможности связать пользователя, создавшего сниппет, с экземпляром сниппета. Пользователь не передается как часть сериализованного представления, а является свойством входящего запроса. Мы решаем эту проблему путем переопределения метода `.perform_create()` в наших представлениях фрагментов, что позволяет нам изменять способ сохранения экземпляра и обрабатывать любую информацию, которая подразумевается во входящем запросе или запрашиваемом URL. В классе представления `SnippetList` добавьте следующий метод: ```python def perform_create(self, serializer): serializer.save(owner=self.request.user) ``` Теперь методу `create()` нашего сериализатора будет передано дополнительное поле `'owner'` вместе с провалидированными данными из запроса. ## Обновление нашего сериализатора Теперь, когда сниппеты связаны с пользователем, который их создал, давайте обновим наш `SnippetSerializer`, чтобы отразить это. Добавьте следующее поле в определение сериализатора в `serializers.py`: ```python owner = serializers.ReadOnlyField(source='owner.username') ``` **Примечание**: Убедитесь, что вы также добавили `'owner'` в список полей во внутреннем классе `Meta`. В этом поле происходит нечто весьма интересное. Аргумент `source` управляет тем, какой атрибут используется для заполнения поля, и может указывать на любой атрибут сериализованного экземпляра. Он также может принимать точечную нотацию, показанную выше, в этом случае он будет перебирать заданные атрибуты, подобно тому, как это используется в языке шаблонов Django. Поле, которое мы добавили, представляет собой нетипизированный класс `ReadOnlyField`, в отличие от других типизированных полей, таких как `CharField`, `BooleanField` и т.д.. Нетипизированное `ReadOnlyField` всегда только для чтения, оно будет использоваться для сериализованных представлений, но не будет использоваться для обновления экземпляров модели при их десериализации. Мы могли бы также использовать здесь `CharField(read_only=True)`. ## Добавление необходимых разрешений к представлениям Теперь, когда фрагменты кода связаны с пользователями, мы хотим убедиться, что только аутентифицированные пользователи могут создавать, обновлять и удалять фрагменты кода. DRF включает в себя ряд классов разрешений, которые мы можем использовать для ограничения доступа к определенному представлению. В данном случае нам нужен класс `IsAuthenticatedOrReadOnly`, который обеспечит аутентифицированным запросам доступ на чтение-запись, а неаутентифицированным - только на чтение. Сначала добавьте следующий импорт в модуль views ```python from rest_framework import permissions ``` Затем добавьте следующее свойство к **обоим** классам представления `SnippetList` и `SnippetDetail`. ```python permission_classes = [permissions.IsAuthenticatedOrReadOnly] ``` ## Добавление входа в Web-интерфейс API Если вы откроете браузер и перейдете к Web-интерфейсу API, то обнаружите, что больше не можете создавать новые фрагменты кода. Для этого нам нужно иметь возможность войти в систему как пользователь. Мы можем добавить представление входа для использования с Web-интерфейсом API, отредактировав URLconf в нашем файле `urls.py` на уровне проекта. Добавьте следующий импорт в верхней части файла: ```python from django.urls import path, include ``` И в конце файла добавьте шаблон для включения представлений входа и выхода для Web-интерфейса API. ```python urlpatterns += [ path('api-auth/', include('rest_framework.urls')), ] ``` Часть шаблона `'api-auth/'` может быть любым URL, который вы хотите использовать. Теперь, если вы снова откроете браузер и обновите страницу, вы увидите ссылку 'Login' в правом верхнем углу страницы. Если вы войдете в систему как один из пользователей, созданных ранее, вы снова сможете создавать фрагменты кода. После создания нескольких фрагментов кода перейдите к конечной точке `'/users/'` и обратите внимание, что представление включает список идентификаторов фрагментов, связанных с каждым пользователем, в поле 'snippets' каждого пользователя. ## Разрешения на уровне объекта Нам бы хотелось, чтобы все кодовые сниппеты были видны всем, но при этом чтобы только пользователь, создавший кодовый сниппет, мог его обновить или удалить. Для этого нам понадобится создать пользовательское разрешение. В приложении `snippets` создайте новый файл `permissions.py`. ```python from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): """ Custom permission to only allow owners of an object to edit it. """ def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request, # so we'll always allow GET, HEAD or OPTIONS requests. if request.method in permissions.SAFE_METHODS: return True # Write permissions are only allowed to the owner of the snippet. return obj.owner == request.user ``` Теперь мы можем добавить это пользовательское разрешение в конечную точку экземпляра сниппета, отредактировав свойство `permission_classes` в классе представления `SnippetDetail`: ```python permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly] ``` Не забудьте также импортировать класс `IsOwnerOrReadOnly`. ```python from snippets.permissions import IsOwnerOrReadOnly ``` Теперь, если вы снова откроете браузер, вы обнаружите, что действия 'DELETE' и 'PUT' появляются на конечной точке экземпляра сниппета, только если вы вошли в систему как тот же пользователь, который создал сниппет кода. ## Аутентификация в API Поскольку теперь у нас есть набор прав доступа к API, нам нужно аутентифицировать наши запросы к нему, если мы хотим редактировать какие-либо сниппеты. Мы не установили никаких [классов аутентификации](../api-guide/authentication.md), поэтому сейчас применяются значения по умолчанию, а именно `SessionAuthentication` и `BasicAuthentication`. Когда мы взаимодействуем с API через веб-браузер, мы можем войти в систему, и тогда сессия браузера обеспечит необходимую аутентификацию для запросов. Если мы взаимодействуем с API программно, нам необходимо явно предоставлять учетные данные аутентификации при каждом запросе. Если мы попытаемся создать сниппет без аутентификации, мы получим ошибку: ```bash http POST http://127.0.0.1:8000/snippets/ code="print(123)" { "detail": "Authentication credentials were not provided." } ``` Мы можем сделать успешный запрос, включив в него имя пользователя и пароль одного из пользователей, созданных нами ранее. ```bash http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)" { "id": 1, "owner": "admin", "title": "foo", "code": "print(789)", "linenos": false, "language": "python", "style": "friendly" } ``` ## Резюме Теперь у нас есть довольно тонкий набор разрешений для нашего Web API, а также конечные точки для пользователей системы и для созданных ими фрагментов кода. В [части 5](5-relationships-and-hyperlinked-apis.md) учебника мы рассмотрим, как мы можем связать все вместе, создав конечную точку HTML для наших выделенных фрагментов, и улучшить связность нашего API, используя гиперссылки для связей внутри системы. ================================================ FILE: tutorial/5-relationships-and-hyperlinked-apis.md ================================================ # Урок 5: Отношения и API с гиперссылками В настоящее время отношения в нашем API представлены с помощью первичных ключей. В этой части учебника мы улучшим связность и наглядность нашего API, используя вместо этого гиперссылки для отношений. ## Создание конечной точки для корня нашего API Сейчас у нас есть конечные точки для "сниппетов" и "пользователей", но у нас нет единой точки входа в наш API. Чтобы создать ее, мы воспользуемся обычным представлением на основе функций и декоратором `@api_view`, который мы представили ранее. В вашем `snippets/views.py` добавьте: ```python from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.reverse import reverse @api_view(['GET']) def api_root(request, format=None): return Response( { 'users': reverse('user-list', request=request, format=format), 'snippets': reverse('snippet-list', request=request, format=format) } ) ``` Здесь следует отметить два момента. Во-первых, мы используем функцию DRF `reverse`, чтобы вернуть полностью квалифицированные URL; во-вторых, шаблоны URL идентифицируются удобными именами, которые мы объявим позже в нашем `snippets/urls.py`. ## Создание конечной точки для подсвеченных фрагментов Другая очевидная вещь, которой все еще не хватает в нашем API pastebin, — это конечные точки подсветки кода. В отличие от всех других конечных точек API, мы не хотим использовать JSON, а вместо этого просто представим HTML-представление. Существует два способа рендеринга HTML, предоставляемых DRF: один для работы с HTML, созданным с помощью шаблонов, другой для работы с предварительно созданным HTML. Для этой конечной точки мы хотим использовать второй рендерер. Другая вещь, которую мы должны учитывать при создании представления подсветки кода, заключается в том, что нет существующего конкретного общего представления, которое мы могли бы использовать. Мы возвращаем не экземпляр объекта, а свойство экземпляра объекта. Вместо того чтобы использовать конкретное общее представление, мы будем использовать базовый класс для представления экземпляров и создадим свой собственный метод `.get()`. В вашем `snippets/views.py` добавьте: ```python from rest_framework import renderers class SnippetHighlight(generics.GenericAPIView): queryset = Snippet.objects.all() renderer_classes = [renderers.StaticHTMLRenderer] def get(self, request, *args, **kwargs): snippet = self.get_object() return Response(snippet.highlighted) ``` Как обычно, нам нужно добавить новые представления, которые мы создали, в наш URLconf. Мы добавим шаблон url для нашего нового корня API в `snippets/urls.py`: ```python path('', views.api_root), ``` А затем добавьте шаблон url для выделения фрагмента: ```python path('snippets//highlight/', views.SnippetHighlight.as_view()), ``` ## Гиперссылка на наш API Работа с отношениями между сущностями - один из самых сложных аспектов разработки Web API. Существует множество различных способов, которые мы можем выбрать для отображения отношений: * Использование первичных ключей. * Использование гиперссылок между сущностями. * Использование уникального идентифицирующего поля slug в связанной сущности. * Использование стандартного строкового представления связанной сущности. * Вложение связанной сущности в родительское представление. * Другое пользовательское представление. DRF поддерживает все эти стили и может применять их к прямым или обратным отношениям, или применять их к пользовательским менеджерам, таким как общие внешние ключи. В данном случае мы хотели бы использовать гиперссылки между сущностями. Для этого мы изменим наши сериализаторы, чтобы расширить `HyperlinkedModelSerializer` вместо существующего `ModelSerializer`. `HyperlinkedModelSerializer` имеет следующие отличия от `ModelSerializer`: * По умолчанию он не включает поле `id`. * Он включает поле `url`, используя `HyperlinkedIdentityField`. * Отношения используют `HyperlinkedRelatedField`, вместо `PrimaryKeyRelatedField`. Мы можем легко переписать наши существующие сериализаторы для использования гиперссылок. В вашем `snippets/serializers.py` добавьте: ```python class SnippetSerializer(serializers.HyperlinkedModelSerializer): owner = serializers.ReadOnlyField(source='owner.username') highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html') class Meta: model = Snippet fields = [ 'url', 'id', 'highlight', 'owner', 'title', 'code', 'linenos', 'language', 'style', ] class UserSerializer(serializers.HyperlinkedModelSerializer): snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True) class Meta: model = User fields = [ 'url', 'id', 'username', 'snippets', ] ``` Обратите внимание, что мы также добавили новое поле `'highlight'`. Это поле того же типа, что и поле `url`, за исключением того, что оно указывает на шаблон url `'snippet-highlight'`, а не на шаблон url `'snippet-detail'`. Поскольку мы включили URL с суффиксом формата, например, `'.json'`, нам также необходимо указать полю `highlight`, что все гиперссылки с суффиксом формата, которые оно возвращает, должны использовать суффикс `'.html'`. --- **Обратите внимание:** Когда вы вручную создаете экземпляры этих сериализаторов внутри своих представлений (например, в `SnippetDetail` или `SnippetList`), вы **обязательно должны** передать `context={'request': request}`, чтобы сериализатор знал, как создавать абсолютные URL-адреса. Например, вместо: ```python serializer = SnippetSerializer(snippet) ``` Вы должны написать: ```python serializer = SnippetSerializer(snippet, context={'request': request}) ``` Если ваше представление является подклассом `GenericAPIView`, вы можете использовать `get_serializer_context()` в качестве вспомогательного метода. --- ## Убедитесь, что наши шаблоны URL названы Если мы собираемся иметь API с гиперссылками, нам нужно убедиться, что мы назвали наши шаблоны URL. Давайте рассмотрим, какие шаблоны URL нам нужно назвать. * Корень нашего API ссылается на `'user-list'` и `'snippet-list'`. * Наш сериализатор сниппетов включает поле, которое ссылается на `'snippet-highlight'`. * Наш сериализатор пользователей включает поле, которое ссылается на `'snippet-detail'`. * Наши сериализаторы сниппетов и пользователей включают поля `'url'`, которые по умолчанию ссылаются на `'{model_name}-detail'`, что в данном случае будет `'snippet-detail'` и `'user-detail'`. После добавления всех этих имен в нашу URLconf, наш файл `snippets/urls.py` должен выглядеть следующим образом: ```python from django.urls import path from rest_framework.urlpatterns import format_suffix_patterns from snippets import views # API endpoints urlpatterns = format_suffix_patterns([ path( '', views.api_root, ), path( 'snippets/', views.SnippetList.as_view(), name='snippet-list', ), path( 'snippets//', views.SnippetDetail.as_view(), name='snippet-detail', ), path( 'snippets//highlight/', views.SnippetHighlight.as_view(), name='snippet-highlight', ), path( 'users/', views.UserList.as_view(), name='user-list', ), path( 'users//', views.UserDetail.as_view(), name='user-detail', ) ]) ``` ## Добавление пагинации Представления списка для пользователей и фрагментов кода могут в конечном итоге возвращать довольно много экземпляров, поэтому на самом деле мы хотели бы убедиться, что мы постранично отображаем результаты, и позволить клиенту API пройтись по каждой отдельной странице. Мы можем изменить тип списка по умолчанию на использование пагинации, слегка изменив наш файл `tutorial/settings.py`. Добавьте следующую настройку: ```python REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10 } ``` Обратите внимание, что все настройки DRF разнесены по именам в один словарь с именем `REST_FRAMEWORK`, что позволяет отделить их от других настроек проекта. При необходимости мы также можем настроить стиль пагинации, но в данном случае мы будем придерживаться стиля по умолчанию. ## Просмотр API Если мы откроем браузер и перейдем к API с возможностью просмотра, вы увидите, что теперь вы можете работать с API, просто переходя по ссылкам. Вы также сможете увидеть ссылки "highlight" на экземплярах сниппетов, которые приведут вас к HTML-представлениям выделенного кода. В [части 6](6-viewsets-and-routers.md) учебника мы рассмотрим, как мы можем использовать ViewSets и Routers для уменьшения количества кода, необходимого для создания нашего API. ================================================ FILE: tutorial/6-viewsets-and-routers.md ================================================ # Урок 6: Наборы представлений и маршрутизаторы DRF включает в себя абстракцию для работы с `ViewSets`, которая позволяет разработчику сосредоточиться на моделировании состояния и взаимодействия API, а построение URL-адресов оставить на автоматическое управление, основанное на общих соглашениях. Классы `ViewSet` - это почти то же самое, что и классы `View`, за исключением того, что они предоставляют такие операции, как `retrieve` или `update`, а не обработчики методов, таких как `get` или `put`. Класс `ViewSet` привязывается к набору обработчиков методов только в последний момент, когда он инстанцируется в набор представлений, обычно с помощью класса `Router`, который обрабатывает все сложности определения URL conf за вас. ## Рефакторинг для использования ViewSets Давайте возьмем наш текущий набор представлений и переделаем их в наборы представлений. Прежде всего, давайте переделаем наши классы `UserList` и `UserDetail` в один класс `UserViewSet`. В файле `snippets/views.py` мы можем удалить два класса view и заменить их одним классом ViewSet: ```python from rest_framework import viewsets class UserViewSet(viewsets.ReadOnlyModelViewSet): """ This viewset automatically provides `list` and `retrieve` actions. """ queryset = User.objects.all() serializer_class = UserSerializer ``` Здесь мы использовали класс `ReadOnlyModelViewSet` для автоматического обеспечения операций по умолчанию "только для чтения". Мы по-прежнему задаем атрибуты `queryset` и `serializer_class` точно так же, как и при использовании обычных представлений, но нам больше не нужно предоставлять одну и ту же информацию двум отдельным классам. Далее мы заменим классы представлений `SnippetList`, `SnippetDetail` и `SnippetHighlight`. Мы можем удалить эти три класса и снова заменить их одним. ```python from rest_framework import permissions from rest_framework import renderers from rest_framework.decorators import action from rest_framework.response import Response class SnippetViewSet(viewsets.ModelViewSet): """ This ViewSet automatically provides `list`, `create`, `retrieve`, `update` and `destroy` actions. Additionally we also provide an extra `highlight` action. """ queryset = Snippet.objects.all() serializer_class = SnippetSerializer permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly] @action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer]) def highlight(self, request, *args, **kwargs): snippet = self.get_object() return Response(snippet.highlighted) def perform_create(self, serializer): serializer.save(owner=self.request.user) ``` На этот раз мы использовали класс `ModelViewSet`, чтобы получить полный набор стандартных операций чтения и записи. Обратите внимание, что мы также использовали декоратор `@action` для создания пользовательского действия с именем `highlight`. Этот декоратор можно использовать для добавления любых пользовательских конечных точек, которые не вписываются в стандартный стиль `create`/`update`/`delete`. Пользовательские действия, использующие декоратор `@action`, по умолчанию будут отвечать на `GET` запросы. Мы можем использовать аргумент `methods`, если хотим получить действие, отвечающее на `POST` запросы. URL для пользовательских действий по умолчанию зависит от имени метода. Если вы хотите изменить способ построения URL, вы можете включить `url_path` в качестве именованного аргумента декоратора. ## Привязка наборов представлений к URL-адресам в явном виде Методы обработчика привязываются к действиям только после определения URLConf. Чтобы увидеть, что происходит под капотом, давайте сначала явно создадим набор представлений из наших ViewSet. В файле `snippets/urls.py` мы связываем наши классы `ViewSet` в набор конкретных представлений. ```python from rest_framework import renderers from snippets.views import api_root, SnippetViewSet, UserViewSet snippet_list = SnippetViewSet.as_view( {'get': 'list', 'post': 'create'} ) snippet_detail = SnippetViewSet.as_view( {'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'} ) snippet_highlight = SnippetViewSet.as_view( {'get': 'highlight'}, renderer_classes=[renderers.StaticHTMLRenderer] ) user_list = UserViewSet.as_view( {'get': 'list'} ) user_detail = UserViewSet.as_view( {'get': 'retrieve'} ) ``` Обратите внимание, как мы создаем несколько представлений из каждого класса `ViewSet`, привязывая HTTP-методы к требуемым действиям для каждого представления. Теперь, когда мы связали наши ресурсы с конкретными представлениями, мы можем зарегистрировать представления с помощью конфигурации URL, как обычно. ```python urlpatterns = format_suffix_patterns([ path('', api_root), path('snippets/', snippet_list, name='snippet-list'), path('snippets//', snippet_detail, name='snippet-detail'), path('snippets//highlight/', snippet_highlight, name='snippet-highlight'), path('users/', user_list, name='user-list'), path('users//', user_detail, name='user-detail') ]) ``` ## Использование маршрутизаторов Поскольку мы используем классы `ViewSet`, а не `View`, нам не нужно самим разрабатывать конфигурацию URL. Соглашения о соединении ресурсов в представления и URL-адреса могут быть обработаны автоматически, с помощью класса `Router`. Все, что нам нужно сделать, это зарегистрировать соответствующие наборы представлений в маршрутизаторе, и пусть он сделает все остальное. Вот наш переделанный файл `snippets/urls.py`. ```python from django.urls import path, include from rest_framework.routers import DefaultRouter from snippets import views # Create a router and register our ViewSets with it. router = DefaultRouter() router.register(r'snippets', views.SnippetViewSet, basename='snippet') router.register(r'users', views.UserViewSet, basename='user') # The API URLs are now determined automatically by the router. urlpatterns = [ path('', include(router.urls)), ] ``` Регистрация наборов представлений в маршрутизаторе аналогична заданию url-шаблона. Мы указываем два аргумента - префикс URL для представлений и сам набор представлений. Класс `DefaultRouter`, который мы используем, также автоматически создает для нас корневое представление API, поэтому мы можем удалить функцию `api_root` из нашего модуля `views`. ## Компромиссы между представлениями и наборами представлений Использование `ViewSets` может быть действительно полезной абстракцией. Она помогает обеспечить согласованность соглашений URL в вашем API, минимизирует объем кода, который вам нужно написать, и позволяет вам сосредоточиться на взаимодействии и представлениях, которые предоставляет ваш API, а не на специфике URL conf. Но это не значит, что такой подход всегда правильный. Существует аналогичный набор компромиссов, которые необходимо учитывать при использовании представлений на основе классов вместо представлений на основе функций. Использование наборов представлений менее очевидно, чем создание представлений API по отдельности.