-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Add models for GitHub App #12070
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add models for GitHub App #12070
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
GITHUB = "github" | ||
GITHUB_APP = "githubapp" | ||
GITLAB = "gitlab" | ||
BITBUCKET = "bitbucket" | ||
|
||
VCS_PROVIDER_CHOICES = ( | ||
(GITHUB, "GitHub"), | ||
(GITHUB_APP, "GitHub"), | ||
(GITLAB, "GitLab"), | ||
(BITBUCKET, "Bitbucket"), | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
# Generated by Django 4.2.18 on 2025-02-03 21:58 | ||
import django.db.models.deletion | ||
import django_extensions.db.fields | ||
from django.db import migrations | ||
from django.db import models | ||
from django_safemigrate import Safe | ||
|
||
|
||
class Migration(migrations.Migration): | ||
safe = Safe.before_deploy | ||
|
||
dependencies = [ | ||
("oauth", "0017_remove_unused_indexes"), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="GitHubAppInstallation", | ||
fields=[ | ||
( | ||
"id", | ||
models.AutoField( | ||
auto_created=True, | ||
primary_key=True, | ||
serialize=False, | ||
verbose_name="ID", | ||
), | ||
), | ||
( | ||
"created", | ||
django_extensions.db.fields.CreationDateTimeField( | ||
auto_now_add=True, verbose_name="created" | ||
), | ||
), | ||
( | ||
"modified", | ||
django_extensions.db.fields.ModificationDateTimeField( | ||
auto_now=True, verbose_name="modified" | ||
), | ||
), | ||
( | ||
"installation_id", | ||
models.PositiveBigIntegerField( | ||
db_index=True, | ||
help_text="The application installation ID", | ||
unique=True, | ||
), | ||
), | ||
( | ||
"target_id", | ||
models.PositiveBigIntegerField( | ||
help_text="A GitHub account ID, it can be from a user or an organization" | ||
), | ||
), | ||
( | ||
"target_type", | ||
models.CharField( | ||
choices=[("User", "User"), ("Organization", "Organization")], | ||
help_text="Account type that the target_id belongs to (user or organization)", | ||
max_length=255, | ||
), | ||
), | ||
( | ||
"extra_data", | ||
models.JSONField( | ||
default=dict, | ||
help_text="Extra data returned by the webhook when the installation is created", | ||
), | ||
), | ||
], | ||
options={ | ||
"get_latest_by": "modified", | ||
"verbose_name": "GitHub app installation", | ||
"abstract": False, | ||
}, | ||
), | ||
migrations.AddField( | ||
model_name="remoterepository", | ||
name="github_app_installation", | ||
field=models.ForeignKey( | ||
blank=True, | ||
null=True, | ||
on_delete=django.db.models.deletion.CASCADE, | ||
related_name="repositories", | ||
to="oauth.githubappinstallation", | ||
verbose_name="GitHub App Installation", | ||
), | ||
), | ||
migrations.AlterField( | ||
model_name="remoteorganization", | ||
name="vcs_provider", | ||
field=models.CharField( | ||
choices=[ | ||
("github", "GitHub"), | ||
("githubapp", "GitHub"), | ||
("gitlab", "GitLab"), | ||
("bitbucket", "Bitbucket"), | ||
], | ||
max_length=32, | ||
verbose_name="VCS provider", | ||
), | ||
), | ||
migrations.AlterField( | ||
model_name="remoterepository", | ||
name="vcs_provider", | ||
field=models.CharField( | ||
choices=[ | ||
("github", "GitHub"), | ||
("githubapp", "GitHub"), | ||
("gitlab", "GitLab"), | ||
("bitbucket", "Bitbucket"), | ||
], | ||
max_length=32, | ||
verbose_name="VCS provider", | ||
), | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,71 @@ | |
log = structlog.get_logger(__name__) | ||
|
||
|
||
class GitHubAppInstallationManager(models.Manager): | ||
def get_or_create_installation( | ||
self, *, installation_id, target_id, target_type, extra_data=None | ||
): | ||
""" | ||
Get or create a GitHub app installation. | ||
|
||
Only the installation_id is unique, the target_id and target_type could change, | ||
but this should never happen. | ||
""" | ||
installation, created = self.get_or_create( | ||
installation_id=installation_id, | ||
defaults={ | ||
"target_id": target_id, | ||
"target_type": target_type, | ||
"extra_data": extra_data or {}, | ||
}, | ||
) | ||
# NOTE: An installation can't change its target_id or target_type. | ||
# This should never happen, unless this assumption is wrong. | ||
if installation.target_id != target_id or installation.target_type != target_type: | ||
log.exception( | ||
"Installation target_id or target_type changed", | ||
stsewd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
installation_id=installation.installation_id, | ||
target_id=installation.target_id, | ||
target_type=installation.target_type, | ||
new_target_id=target_id, | ||
new_target_type=target_type, | ||
) | ||
installation.target_id = target_id | ||
installation.target_type = target_type | ||
installation.save() | ||
return installation, created | ||
|
||
|
||
class GitHubAccountType(models.TextChoices): | ||
USER = "User", _("User") | ||
ORGANIZATION = "Organization", _("Organization") | ||
|
||
|
||
class GitHubAppInstallation(TimeStampedModel): | ||
installation_id = models.PositiveBigIntegerField( | ||
help_text=_("The application installation ID"), | ||
unique=True, | ||
db_index=True, | ||
) | ||
target_id = models.PositiveBigIntegerField( | ||
help_text=_("A GitHub account ID, it can be from a user or an organization"), | ||
) | ||
target_type = models.CharField( | ||
help_text=_("Account type that the target_id belongs to (user or organization)"), | ||
choices=GitHubAccountType.choices, | ||
max_length=255, | ||
) | ||
extra_data = models.JSONField( | ||
help_text=_("Extra data returned by the webhook when the installation is created"), | ||
default=dict, | ||
) | ||
|
||
objects = GitHubAppInstallationManager() | ||
|
||
class Meta(TimeStampedModel.Meta): | ||
verbose_name = _("GitHub app installation") | ||
|
||
|
||
class RemoteOrganization(TimeStampedModel): | ||
""" | ||
Organization from remote service. | ||
|
@@ -173,6 +238,17 @@ class RemoteRepository(TimeStampedModel): | |
remote_id = models.CharField(max_length=128) | ||
vcs_provider = models.CharField(_("VCS provider"), choices=VCS_PROVIDER_CHOICES, max_length=32) | ||
|
||
github_app_installation = models.ForeignKey( | ||
GitHubAppInstallation, | ||
verbose_name=_("GitHub App Installation"), | ||
related_name="repositories", | ||
null=True, | ||
blank=True, | ||
# When an installation is deleted, we delete all its remote repositories | ||
# and relations, users will need to manually link the projects to each repository again. | ||
Comment on lines
+247
to
+248
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a nicer UI we can build for that in the future? Seems like a pretty large downside. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe something that facilitates re-connection in batch, maybe could be exposed as an API to start. But I only see this a problem for users with lots of repos connected, re-connection is straightforward, just select the repo from the settings and click save. |
||
on_delete=models.CASCADE, | ||
) | ||
|
||
objects = RemoteRepositoryQuerySet.as_manager() | ||
|
||
class Meta: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh... foreboding.