Какой смысл collectstatic джанго?

Создание объектов¶

Чтобы создать новый экземпляр модели, создайте его экземпляр, как и любой другой класс Python:

class (**kwargs)

Ключевыми аргументами являются просто имена полей, которые вы определили в вашей модели

Обратите внимание, что создание экземпляра модели никоим образом не затрагивает вашу базу данных; для этого вам необходимо выполнить

Примечание

Вы можете испытать желание изменить модель, переопределив метод . Однако, если вы сделаете это, позаботьтесь о том, чтобы не изменять сигнатуру вызывающего, так как любое изменение может помешать сохранению экземпляра модели. Вместо того, чтобы переопределять , попробуйте использовать один из следующих подходов:

  1. Добавьте метод в класс модели:

    from django.db import models
    
    class Book(models.Model):
        title = models.CharField(max_length=100)
    
        @classmethod
        def create(cls, title):
            book = cls(title=title)
            # do something with the book
            return book
    
    book = Book.create("Pride and Prejudice")
    
  2. Добавить метод в пользовательский менеджер (обычно предпочтительнее):

    class BookManager(models.Manager):
        def create_book(self, title):
            book = self.create(title=title)
            # do something with the book
            return book
    
    class Book(models.Model):
        title = models.CharField(max_length=100)
    
        objects = BookManager()
    
    book = Book.objects.create_book("Pride and Prejudice")
    

Post

Теперь мы переходим к модели постов. Как и в случае с пользователями, мы создаем модуль для постов, а также для комментариев и категорий, поскольку они нам понадобятся:

Добавим следующий код в posts/models.py:

Для этой модели мы импортируем slugify для создания ЧПУ, класс User (так как пост будет создаваться пользователем), и тип данных RichTextField, который является полем, взятым из библиотеки, импортированной в первой части.

Далее мы создадим класс Post и свяжем пользователей с постами с помощью функции ForeignKey. Это будет отношение 1 к N, поскольку пользователь может создать несколько постов, а пост принадлежит одному пользователю. Как и в модели пользователя, мы будем использовать PROTECT, поскольку не хотим потерять записи.

Другие поля:

  • title: Тип поля CharField с максимальным размером 255 символов.
  • image_header: Это будет заголовок нашего поста, который мы сохраним в media/posts/photos.
  • post: Это поле будет типа RichTextField, что позволит нам создавать тексты с различными размерами, шрифтами, цветами и т.д.
  • date_created: Дата создания. Текущая дата будет сохранена по умолчанию атрибутом auto_now_add.
  • date_modified: Дата изменения. Текущая дата будет сохраняться по умолчанию при каждом обновлении поста с помощью атрибута auto_now.
  • is_draft: Булево поле, мы будем сохранять, если статья является черновиком или мы хотим опубликовать ее на сайте.
  • url: Типа slugField, здесь мы будем хранить url, он будет создан из заголовка.
  • views: Количество просмотров.
  • categories: Список категорий, присваиваемых посту, это отношение типа N к N, так как пост может иметь несколько категорий, а категория может принадлежать нескольким постам.

Затем мы объявляем класс Meta и добавляем параметр order by title, это будет означать, что когда мы возвращаем список объектов типа post, по умолчанию он будет сортироваться по записям.

Метод __str__, о котором мы рассказывали ранее.

Метод save. Здесь мы делаем следующее: когда мы собираемся сохранить пост, мы передаем функцию slugify для заголовка и сохраняем его в поле url.

Обновление объектов из базы ¶

Если вы удалите поле из экземпляра шаблона, новый доступ перезагрузит значение из базы данных:

>>> obj = MyModel.objects.first()
>>> del obj.field
>>> obj.field  # Loads the field from the database
( используется = None , fields = None )

Если вам нужно повторно загрузить значения модели из базы данных, вы можете использовать этот метод . Когда этот метод вызывается без параметров, происходит следующее:

  1. Все неотложенные поля модели обновляются значениями, которые в настоящее время присутствуют в базе данных.
  2. Все кэшированные отношения удаляются из перезагруженного экземпляра.

Из базы данных перезагружаются только поля модели. Другие значения, зависящие от базы данных, такие как аннотации, не перезагружаются. Не все атрибуты сбрасываются.

Перезагрузка выполняется из базы данных, в которой были созданы существующие значения, или из базы данных по умолчанию, если экземпляр не был загружен из базы данных. Параметр можно использовать для принудительной перезагрузки исходной базы данных.

Можно принудительно перезагрузить группу полей с помощью параметра .

Например, чтобы проверить, действительно ли вызов привел к ожидаемому результату, вы можете написать тест, подобный следующему:

def test_update_result(self):
    obj = MyModel.objects.create(val=1)
    MyModel.objects.filter(pk=obj.pk).update(val=F('val') + 1)
    # At this point obj.val is still 1, but the value in the database
    # was updated to 2. The object's updated value needs to be reloaded
    # from the database.
    obj.refresh_from_db()
    self.assertEqual(obj.val, 2)

Обратите внимание, что при доступе к отложенным полям загрузка отложенного значения поля выполняется с помощью этого метода. Таким образом, можно настроить способ загрузки отложенных данных

В приведенном ниже примере показано, как мы можем перезагрузить все поля экземпляра при перезагрузке отложенного поля:

class ExampleModel(models.Model):
    def refresh_from_db(self, using=None, fields=None, **kwargs):
        # fields contains the name of the deferred field to be
        # loaded.
        if fields is not None
            fields = set(fields)
            deferred_fields = self.get_deferred_fields()
            # If any deferred field is going to be loaded
            if fields.intersection(deferred_fields):
                # then load all of them
                fields = fields.union(deferred_fields)
        super().refresh_from_db(using, fields, **kwargs)
()

Редактирование и удаление объектов модели

Последнее обновление: 27.02.2018

Рассмотрим пример с редактированием и удалением объектов модели. Для этого продолжим работу с проектом из прошлой темы. Вначале добавим в файл
views.py функции, которые будут собственно выполнять редактирование и удаление:

from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.http import HttpResponseNotFound
from .models import Person

# получение данных из бд
def index(request):
    people = Person.objects.all()
    return render(request, "index.html", {"people": people})

# сохранение данных в бд
def create(request):
    if request.method == "POST":
        person = Person()
        person.name = request.POST.get("name")
        person.age = request.POST.get("age")
        person.save()
    return HttpResponseRedirect("/")

# изменение данных в бд
def edit(request, id):
    try:
        person = Person.objects.get(id=id)

        if request.method == "POST":
            person.name = request.POST.get("name")
            person.age = request.POST.get("age")
            person.save()
            return HttpResponseRedirect("/")
        else:
            return render(request, "edit.html", {"person": person})
    except Person.DoesNotExist:
        return HttpResponseNotFound("<h2>Person not found</h2>")
    
# удаление данных из бд
def delete(request, id):
    try:
        person = Person.objects.get(id=id)
        person.delete()
        return HttpResponseRedirect("/")
    except Person.DoesNotExist:
        return HttpResponseNotFound("<h2>Person not found</h2>")

Первые две функции — create и index остаются те же самые, что и в прошлой теме.

Функция выполняет редактирование объекта. Функция в качестве параметра принимает идентификатор объекта в базе данных. И вначале по этому идентификатору
мы пытаемся найти объект с помощью метода . Поскольку в случае отсутствия объекта мы можем столкнуться с исключением Person.DoesNotExist,
то соответственно нам надо обработать подобное исключение, если вдруг будет передан несуществующий идентификатор. И если объект не будет найден,
то пользователю возващается ошибка 404 через вызов .

Если объект найден, то обработка делится на две ветви. Если запрос POST, то есть если пользователь отправил новые изменненые данные для объекта, то сохраняем
эти данные в бд и выполняем переадресацию на корень веб-сайта. Если запрос GET, то отображаем пользователю страницу edit.html с формой для редактирования объекта.

Функция аналогичным образом находит объет и выполняет его удаление.

Теперь добавим в папку templates файл edit.html со следующим содержимым:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Модели в Django</title>
</head>
<body class="container">
    <form method="POST">
        {% csrf_token %}
        <p>
            <label>Введите имя</label><br>
            <input type="text" name="name" value="`person`.`name`" />
        </p>
        <p>
            <label>Введите возраст</label><br>
            <input type="number" name="age" value="`person`.`age`" />
        </p>
        <input type="submit" value="Сохранить" >
    </form>
</body>
</html>

Здесь определена форма для редактирования объекта. По нажатию на кнопку введенные на форму данные будут уходить по тому же адресу в запросе POST.

Чтобы не вводить вручную адреса для редактирования и удаления объектов изменим шаблон index.html, где выводится список объектов,
добавив в него необходимые ссылки:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Модели в Django</title>
</head>
<body class="container">
    <form method="POST" action="create/">
        {% csrf_token %}
        <p>
            <label>Введите имя</label><br>
            <input type="text" name="name" />
        </p>
        <p>
            <label>Введите возраст</label><br>
            <input type="number" name="age" />
        </p>
        <input type="submit" value="Сохранить" >
    </form>
    {% if people.count > 0 %}
    <h2>Список пользователей</h2>
    <table>
        <thead><th>Id</th><th>Имя</th><th>Возраст</th><th></th></thead>
        {% for person in people %}
        <tr>
            <td>` person`.`id `</td>
            <td>` person`.`name `</td>
            <td>` person`.`age `</td>
            <td><a href="edit/`person`.`id`">Изменить</a> | <a href="delete/`person`.`id`">Удалить</a></td>
        </tr>
        {% endfor %}
    </table>
    {% endif %}
</body>
</html>

Далее в файле urls.py сопоставим функции edit и delete с маршрутами:

from django.urls import path
from firstapp import views

urlpatterns = [
    path('', views.index),
    path('create/', views.create),
    path('edit/<int:id>/', views.edit),
    path('delete/<int:id>/', views.delete),
]

Запустим проект. На главной странице рядом с каждым объектом ообразятся две ссылки:

Нажав на кнопку редактирования, мы перейдем к форме, где мы сможем изменить значения выбранного объекта:

Соответственно нажав на кнопку удаления в таблице объектов, мы удалим выбранный объект.

НазадВперед

Документация Django

Django (Джанго) — свободный фреймворк для веб-приложений на языке Python, использующий шаблон проектирования MVC. Проект поддерживается организацией Django Software Foundation.

Сайт на Django строится из одного или нескольких приложений, которые рекомендуется делать отчуждаемыми и подключаемыми. Это одно из существенных архитектурных отличий этого фреймворка от некоторых других (например, Ruby on Rails). Один из основных принципов фреймворка — DRY (англ. Don’t repeat yourself).

Также, в отличие от других фреймворков, обработчики URL в Django конфигурируются явно при помощи регулярных выражений.

Для работы с базой данных Django использует собственный ORM, в котором модель данных описывается классами Python, и по ней генерируется схема базы данных.

Документация Django

Django (Джанго) — свободный фреймворк для веб-приложений на языке Python, использующий шаблон проектирования MVC. Проект поддерживается организацией Django Software Foundation.

Сайт на Django строится из одного или нескольких приложений, которые рекомендуется делать отчуждаемыми и подключаемыми. Это одно из существенных архитектурных отличий этого фреймворка от некоторых других (например, Ruby on Rails). Один из основных принципов фреймворка — DRY (англ. Don’t repeat yourself).

Также, в отличие от других фреймворков, обработчики URL в Django конфигурируются явно при помощи регулярных выражений.

Для работы с базой данных Django использует собственный ORM, в котором модель данных описывается классами Python, и по ней генерируется схема базы данных.

Обновление объектов из базы данных¶

Если вы удаляете поле из экземпляра модели, при повторном доступе к нему значение из базы данных снова загружается:

>>> obj = MyModel.objects.first()
>>> del obj.field
>>> obj.field  # Loads the field from the database
(using=None, fields=None)

Если вам нужно перезагрузить значения модели из базы данных, вы можете использовать метод . Когда этот метод вызывается без аргументов, выполняется следующее:

  1. Все неотложенные поля модели обновляются до значений, присутствующих в настоящее время в базе данных.
  2. Все кэшированные отношения очищаются перезагрузкой экземпляра.

Только поля модели загружаются из базы данных. Другие зависящие от базы данных значения, такие как аннотации, не перезагружаются. Любые атрибуты также не очищаются.

Перезагрузка происходит из базы данных, из которой был загружен экземпляр, или из базы данных по умолчанию, если экземпляр не был загружен из базы данных. Аргумент может использоваться для принудительной перезагрузки базы данных.

Можно принудительно загрузить набор полей, используя аргумент .

Например, чтобы проверить, что вызов привел к ожидаемому обновлению, вы можете написать тест, подобный следующему:

def test_update_result(self):
    obj = MyModel.objects.create(val=1)
    MyModel.objects.filter(pk=obj.pk).update(val=F('val') + 1)
    # At this point obj.val is still 1, but the value in the database
    # was updated to 2. The object's updated value needs to be reloaded
    # from the database.
    obj.refresh_from_db()
    self.assertEqual(obj.val, 2)

Обратите внимание, что при доступе к отложенным полям загрузка значения отложенного поля происходит с помощью этого метода. Таким образом, можно настроить способ отложенной загрузки

В приведенном ниже примере показано, как можно перезагрузить все поля экземпляра при перезагрузке отложенного поля:

class ExampleModel(models.Model):
    def refresh_from_db(self, using=None, fields=None, **kwargs):
        # fields contains the name of the deferred field to be
        # loaded.
        if fields is not None
            fields = set(fields)
            deferred_fields = self.get_deferred_fields()
            # If any deferred field is going to be loaded
            if fields.intersection(deferred_fields):
                # then load all of them
                fields = fields.union(deferred_fields)
        super().refresh_from_db(using, fields, **kwargs)
()

ЗаказФильтр¶

Класс поддерживает простое упорядочивание результатов, управляемое параметрами запроса.

По умолчанию параметр запроса называется , но это можно отменить с помощью параметра .

Например, чтобы упорядочить пользователей по имени пользователя:

http://example.com/api/users?ordering=username

Клиент также может указать обратный порядок, добавив к имени поля префикс „-„, как показано ниже:

http://example.com/api/users?ordering=-username

Также можно указать несколько порядков:

http://example.com/api/users?ordering=account,username

Указание того, какие поля могут быть заказаны против

Рекомендуется явно указать, какие поля API должен разрешить в фильтре упорядочивания. Вы можете сделать это, установив атрибут на представлении, как показано ниже:

class UserListView(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filter_backends = filters.OrderingFilter
    ordering_fields = 'username', 'email'

Это помогает предотвратить непредвиденную утечку данных, например, разрешение пользователям делать заказы по хэш-полю пароля или других конфиденциальных данных.

Если вы не укажете атрибут на представлении, класс фильтра по умолчанию позволит пользователю фильтровать по любым читаемым полям на сериализаторе, указанном атрибутом .

Если вы уверены, что кверисет, используемый представлением, не содержит конфиденциальных данных, вы также можете явно указать, что представление должно разрешить упорядочивание по любому полю модели или агрегату кверисета, используя специальное значение .

class BookingsListView(generics.ListAPIView):
    queryset = Booking.objects.all()
    serializer_class = BookingSerializer
    filter_backends = filters.OrderingFilter
    ordering_fields = '__all__'

Дополнительные методы экземпляра¶

В дополнение к , , у объекта модели могут быть некоторые из следующих методов:

()

Для каждого поля, для которого установлено , объект будет иметь метод , где — это имя поля. Этот метод возвращает «удобочитаемое» значение поля.

Например:

from django.db import models

class Person(models.Model):
    SHIRT_SIZES = (
        ('S', 'Small'),
        ('M', 'Medium'),
        ('L', 'Large'),
    )
    name = models.CharField(max_length=60)
    shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES)
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'

Changed in Django 3.1:

Добавлена поддержка и .

(**kwargs)
(**kwargs)

Для каждого и , которые не имеют , объект будет иметь методы и , где — это имя поля. Это возвращает следующий и предыдущий объект относительно поля даты, вызывая исключение , когда это необходимо.

Оба эти метода будут выполнять свои запросы с использованием менеджера по умолчанию для модели. Если вам нужно эмулировать фильтрацию, используемую настраиваемым менеджером, или вы хотите выполнить одноразовую настраиваемую фильтрацию, оба метода также принимают необязательные ключевые аргументы, которые должны иметь формат, описанный в .

Обратите внимание, что в случае идентичных значений даты эти методы будут использовать первичный ключ в качестве прерывателя связей. Это гарантирует, что никакие записи не будут пропущены или дублированы

Это также означает, что вы не можете использовать эти методы на несохраненных объектах.

Остальные методы модели¶

Несколько методов имеют специальное назначение.

()

Метод вызывается когда вы применяете функцию к объекту. Django использует в нескольких местах. В частности, для отображения объектов в интерфейсе администратора Django и в качестве значения, вставляемого в шаблон, при отображении объекта. Поэтому, вы должны всегда возвращать в методе красивое и удобное для восприятия представление объекта.

Например:

from django.db import models
from django.utils.encoding import python_2_unicode_compatible

@python_2_unicode_compatible  # only if you need to support Python 2
class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)

    def __str__(self):
        return '%s%s' % (self.first_name, self.last_name)

Если вам необходима совместимость с Python 2, Можете декорировать ваш класс модели декоратором .

()

Метод проверки равенства по умолчанию работает следующим образом: если два объекта содержат одинаковый первичный ключ и являются экземплярами одно класса, тогда они равны. Для прокси-моделей класс определяется поиском первого не прокси родительского класса. Для всех остальных моделей — это просто класс модели.

Например:

from django.db import models

class MyModel(models.Model):
    id = models.AutoField(primary_key=True)

class MyProxyModel(MyModel):
    class Meta
        proxy = True

class MultitableInherited(MyModel):
    pass

MyModel(id=1) == MyModel(id=1)
MyModel(id=1) == MyProxyModel(id=1)
MyModel(id=1) != MultitableInherited(id=1)
MyModel(id=1) != MyModel(id=2)
()

Метод использует значение первичного ключа. На самом деле выполняется . Если первичный ключ не определен, будет вызвано исключение (иначе разные значения перед и после сохранения объекта, что ).

Проверка объектов¶

Существует три этапа проверки модели:

  1. Проверка полей модели —
  2. Проверка модели полностью —
  3. Проверка уникальности полей —

Все три шага выполняются при вызове метода модели .

Когда вы используете , вызов выполнит эти шаги проверки для всех полей, включенных в форму. Смотрите документацию ModelForm для получения дополнительной информации. Вам нужно только вызывать метод модели , если вы планируете самостоятельно обрабатывать ошибки проверки или если вы исключили поля из , для которых требуется проверка.

(exclude=None, validate_unique=True)

Этот метод вызывает , и (если равно ), в этом порядке и вызывает , который имеет атрибут , содержащий ошибки всех трех этапов.

Необязательный аргумент может использоваться для предоставления списка имен полей, которые можно исключить из проверки и очистки. использует этот аргумент, чтобы исключить проверку полей, которые отсутствуют в вашей форме, поскольку любые возникшие ошибки не могут быть исправлены пользователем.

Обратите внимание, что не будет автоматически вызываться при вызове метода модели. Вам нужно будет вызвать его вручную, если вы хотите запустить одноэтапную проверку моделей для ваших собственных моделей, созданных вручную

Например:

from django.core.exceptions import ValidationError
try
    article.full_clean()
except ValidationError as e
    # Do something based on the errors contained in e.message_dict.
    # Display them to a user, or handle them programmatically.
    pass

Первый шаг, который выполняет , — это очистка каждого отдельного поля.

(exclude=None)

Этот метод проверит все поля в вашей модели. Необязательный аргумент позволяет вам предоставить список имен полей, которые нужно исключить из проверки. Он вызовет , если какие-либо поля не пройдут проверку.

Второй шаг, который выполняет , заключается в вызове . Этот метод должен быть переопределен для выполнения пользовательской проверки вашей модели.

()

Этот метод должен использоваться для обеспечения пользовательской проверки модели и для изменения атрибутов вашей модели, если это необходимо. Например, вы можете использовать его для автоматического предоставления значения для поля или для проверки, которая требует доступа к более чем одному полю:

import datetime
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

class Article(models.Model):
    ...
    def clean(self):
        # Don't allow draft entries to have a pub_date.
        if self.status == 'draft' and self.pub_date is not None
            raise ValidationError(_('Draft entries may not have a publication date.'))
        # Set the pub_date for published items if it hasn't been set already.
        if self.status == 'published' and self.pub_date is None
            self.pub_date = datetime.date.today()

Однако обратите внимание, что как , метод модели не вызывается, когда вы вызываете метод вашей модели. В приведенном выше примере исключение , вызванное , было создано со строкой, поэтому оно будет сохранено в специальном ключе словаря ошибок

Этот ключ используется для ошибок, которые связаны со всей моделью, а не с конкретным полем:

В приведенном выше примере исключение , вызванное , было создано со строкой, поэтому оно будет сохранено в специальном ключе словаря ошибок . Этот ключ используется для ошибок, которые связаны со всей моделью, а не с конкретным полем:

from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
try
    article.full_clean()
except ValidationError as e
    non_field_errors = e.message_dictNON_FIELD_ERRORS

Чтобы назначить исключения конкретному полю, создайте экземпляр со словарем, где ключами являются имена полей. Мы могли бы обновить предыдущий пример, чтобы присвоить ошибку полю :

class Article(models.Model):
    ...
    def clean(self):
        # Don't allow draft entries to have a pub_date.
        if self.status == 'draft' and self.pub_date is not None
            raise ValidationError({'pub_date' _('Draft entries may not have a publication date.')})
        ...

Если вы обнаружите ошибки в нескольких полях во время , вы также можете передать имена полей отображения словаря в ошибки:

raise ValidationError({
    'title' ValidationError(_('Missing title.'), code='required'),
    'pub_date' ValidationError(_('Invalid date.'), code='invalid'),
})

Наконец, проверит любые уникальные ограничения на вашей модели.

(exclude=None)

Этот метод похож на , но проверяет все ограничения уникальности вашей модели вместо отдельных значений полей. Необязательный аргумент позволяет вам предоставить список имен полей, исключаемых из проверки. Он вызовет , если какие-либо поля не пройдут проверку.

Переменные¶

Переменные выглядят так: . Когда механизм шаблонов встречает переменную, он оценивает эту переменную и заменяет ее результатом. Имена переменных состоят из любой комбинации буквенно-цифровых символов и символа подчеркивания (), но не могут начинаться с подчеркивания и не могут быть числом. Точка () также появляется в разделах переменных, хотя это имеет особое значение, как указано ниже

Важно отметить, что в именах переменных нельзя использовать пробелы или знаки препинания

Используйте точку () Для доступа к атрибутам переменной.

За кулисами

Технически, когда система шаблонов встречает точку, она пытается выполнить следующие поиски в следующем порядке:

  • Поиск в словаре
  • Поиск атрибута или метода
  • Поиск по числовому индексу

Если полученное значение вызывается, оно вызывается без аргументов. Результат вызова становится значением шаблона.

Этот порядок поиска может вызвать непредвиденное поведение с объектами, которые переопределяют поиск по словарю. Например, рассмотрим следующий фрагмент кода, который пытается перебрать файл :

{% for k, v in defaultdict.items %}
    Do something with k and v here...
{% endfor %}

Поскольку поиск по словарю выполняется первым, это поведение срабатывает и предоставляет значение по умолчанию вместо использования предполагаемого метода . В этом случае сначала подумайте о преобразовании в словарь.

В приведенном выше примере будет заменен атрибутом объекта .

Если вы используете переменную, которой не существует, система шаблонов вставит значение параметра , который по умолчанию установлен на (пустая строка).

Обратите внимание, что «bar» в выражении шаблона, таком как , будет интерпретироваться как буквальная строка и не будет использовать значение переменной «bar», если она существует в контексте шаблона

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Все про сервера
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: