Skip to content

Commit a66b11e

Browse files
committed
Initial parts of Bitbucket importing
1 parent 044d76b commit a66b11e

File tree

6 files changed

+241
-5
lines changed

6 files changed

+241
-5
lines changed

readthedocs/oauth/utils.py

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,96 @@ def import_github(user, sync):
126126
make_github_project(user=user, org=org_obj, privacy=repo_type, repo_json=repo)
127127
except TypeError, e:
128128
print e
129-
130129

131130
return github_connected
131+
132+
133+
###
134+
### Bitbucket
135+
###
136+
137+
138+
def bitbucket_paginate(session, url):
139+
"""
140+
Scans trough all github paginates results and returns the concatenated
141+
list of results.
142+
143+
:param session: requests client instance
144+
:param url: start url to get the data from.
145+
146+
"""
147+
result = []
148+
while url:
149+
r = session.get(url)
150+
result.extend([r.json()])
151+
next_url = r.json().get('next')
152+
if next_url:
153+
url = next_url
154+
else:
155+
url = None
156+
return result
157+
158+
159+
def make_bitbucket_project(user, org, privacy, repo_json):
160+
log.info('Trying Bitbucket: %s' % repo_json['full_name'])
161+
if (repo_json['is_private'] is True and privacy == 'private' or
162+
repo_json['is_private'] is False and privacy == 'public'):
163+
project, created = GithubProject.objects.get_or_create(
164+
full_name=repo_json['full_name'],
165+
)
166+
if project.organization and project.organization != org:
167+
log.debug('Not importing %s because mismatched orgs' % repo_json['name'])
168+
return None
169+
else:
170+
project.organization = org
171+
project.users.add(user)
172+
project.name = repo_json['name']
173+
project.description = repo_json['description']
174+
project.git_url = repo_json['links']['clone'][0]['href']
175+
project.ssh_url = repo_json['links']['clone'][1]['href']
176+
project.html_url = repo_json['links']['html']['href']
177+
project.json = repo_json
178+
project.save()
179+
return project
180+
else:
181+
log.debug('Not importing %s because mismatched type' % repo_json['name'])
182+
183+
184+
def process_bitbucket_json(user, json, repo_type):
185+
try:
186+
for page in json:
187+
for repo in page['values']:
188+
make_bitbucket_project(user=user, org=None, privacy=repo_type, repo_json=repo)
189+
except TypeError, e:
190+
print e
191+
192+
193+
def import_bitbucket(user, sync):
194+
""" Do the actual github import """
195+
196+
repo_type = getattr(settings, 'GITHUB_PRIVACY', 'public')
197+
tokens = SocialToken.objects.filter(
198+
account__user__username=user.username, app__provider='bitbucket')
199+
bitbucket_connected = False
200+
if tokens.exists():
201+
bitbucket_connected = True
202+
if sync:
203+
token = tokens[0]
204+
session = OAuth2Session(
205+
client_id=token.app.client_id,
206+
token={
207+
'access_token': str(token.token),
208+
'token_type': 'bearer'
209+
}
210+
)
211+
# Get user repos
212+
owner_resp = bitbucket_paginate(session, 'https://bitbucket.org/api/2.0/repositories/{owner}'.format(owner=token.account.uid))
213+
process_bitbucket_json(user, owner_resp, repo_type)
214+
215+
# Get org repos
216+
resp = session.get('https://bitbucket.org/api/1.0/user/privileges/')
217+
for team in resp.json()['teams'].keys():
218+
org_resp = bitbucket_paginate(session, 'https://bitbucket.org/api/2.0/teams/{teamname}/repositories' % team)
219+
process_bitbucket_json(user, org_resp, repo_type)
220+
221+
return bitbucket_connected

readthedocs/projects/urls/private.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@
2929
'projects.views.private.project_import_github',
3030
{'sync': True},
3131
name='projects_sync_github'),
32+
33+
url(r'^import/bitbucket/$',
34+
'projects.views.private.project_import_bitbucket',
35+
{'sync': False},
36+
name='projects_import_bitbucket'),
37+
38+
url(r'^import/bitbucket/sync/$',
39+
'projects.views.private.project_import_bitbucket',
40+
{'sync': True},
41+
name='projects_sync_bitbucket'),
3242

3343
url(r'^(?P<project_slug>[-\w]+)/$',
3444
'projects.views.private.project_manage',

readthedocs/projects/views/private.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,3 +532,33 @@ def project_import_github(request, sync=False):
532532
},
533533
context_instance=RequestContext(request)
534534
)
535+
536+
@login_required
537+
def project_import_bitbucket(request, sync=False):
538+
'''Show form that prefills import form with data from BitBucket'''
539+
540+
bitbucket_connected = oauth_utils.import_bitbucket(user=request.user, sync=sync)
541+
repos = GithubProject.objects.filter(users__in=[request.user])
542+
543+
# Find existing projects that match a repo url
544+
for repo in repos:
545+
ghetto_repo = repo.git_url.replace('git://', '').replace('.git', '')
546+
projects = (Project
547+
.objects
548+
.public(request.user)
549+
.filter(Q(repo__endswith=ghetto_repo) |
550+
Q(repo__endswith=ghetto_repo + '.git')))
551+
if projects:
552+
repo.matches = [project.slug for project in projects]
553+
else:
554+
repo.matches = []
555+
556+
return render_to_response(
557+
'projects/project_import_bitbucket.html',
558+
{
559+
'repos': repos,
560+
'bitbucket_connected': bitbucket_connected,
561+
'sync': sync,
562+
},
563+
context_instance=RequestContext(request)
564+
)

readthedocs/settings/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@
201201
'allauth.account',
202202
'allauth.socialaccount',
203203
'allauth.socialaccount.providers.github',
204-
#'allauth.socialaccount.providers.bitbucket',
204+
'allauth.socialaccount.providers.bitbucket',
205205
#'allauth.socialaccount.providers.twitter',
206206
]
207207

readthedocs/templates/projects/project_import.html

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,26 @@ <h3>{% trans "From GitHub" %}</h3>
4343
<div class="col col-trisect">
4444
<h3>From Bitbucket</h3>
4545

46-
<p>
47-
Coming soon, import your Bitbucket repositories!
48-
</p>
46+
{% if accounts.bitbucket.0 %}
47+
<p>
48+
{% blocktrans %}
49+
Import one of your <b>public Bitbucket repositories</b> to start using your documentation.
50+
{% endblocktrans %}
51+
</p>
52+
53+
<form method="get" action="{% url "projects_import_bitbucket" %}">
54+
<input type="submit" value="{% trans "Import from Bitbucket" %}"/>
55+
</form>
56+
{% else %}
57+
<p>
58+
{% blocktrans %}
59+
Once your account is connected to your Bitbucket account,
60+
you will be able to automatically import and configure your <b>public Bitbucket repositories</b>.
61+
{% endblocktrans %}
62+
<a href="{% provider_login_url "bitbucket" process="connect" next="/dashboard/import/" %}">Connect to Bitbucket</a>
63+
{% trans "to import your first repository." %}
64+
</p>
65+
{% endif %}
4966
</div>
5067

5168
<div class="col col-trisect">
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{% extends "base.html" %}
2+
{% load i18n %}
3+
4+
{% block title %}{% trans "Import a BitBucket project" %}{% endblock %}
5+
6+
{% block extra_links %}
7+
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}lib/markitup/skins/simple/style.css" />
8+
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}lib/markitup/sets/sphinx/editor.css" />
9+
{% endblock %}
10+
11+
{% block extra_scripts %}
12+
<script type="text/javascript" src="{{ MEDIA_URL }}javascript/rtd-import.js"></script>
13+
<script type="text/javascript" src="{{ MEDIA_URL }}lib/markitup/jquery.markitup.pack.js"></script>
14+
<script type="text/javascript" src="{{ MEDIA_URL }}lib/markitup/sets/sphinx/editor.js"></script>
15+
<script type="text/javascript">
16+
$(document).ready(function() {
17+
$("#id_description").markItUp(SphinxDocsSettings); // .markItUp(mySettings);
18+
});
19+
</script>
20+
{% endblock %}
21+
22+
{% block content-header %}<h1>{% trans "Import a BitBucket project" %}</h1>{% endblock %}
23+
24+
{% block content %}
25+
{% if bitbucket_connected %}
26+
{% if not sync %}
27+
<p class="empty">
28+
{% url 'projects_sync_bitbucket' as sync_bitbucket_url %}
29+
{% blocktrans %}Showing your BitBucket projects. <a href="{{ sync_bitbucket_url }}">Sync your BitBucket projects</a> to update them.{% endblocktrans %}
30+
</p>
31+
{% else %}
32+
<p class="empty">
33+
{% blocktrans %}BitBucket projects are now up to date.{% endblocktrans %}
34+
</p>
35+
{% endif %}
36+
<div class="module">
37+
<div class="module-wrapper">
38+
<div class="module-list">
39+
<div class="module-list-wrapper">
40+
<ul>
41+
{% for repo in repos %}
42+
<li class="module-item">
43+
44+
<a class="module-item-title" href="{{ repo.html_url }}">{{ repo.full_name }}</a>
45+
46+
<form method="post" action="{% url "projects_import" %}">
47+
{% csrf_token %}
48+
<input name="name" type="hidden" value="{{ repo.name }}" />
49+
<input name="repo" type="hidden" value="{{ repo.html_url }}" />
50+
<input name="repo_type" type="hidden" value="git" />
51+
<input name="description" type="hidden" value="{{ repo.description }}" />
52+
<input name="project_url" type="hidden" value="{{ repo.homepage }}" />
53+
54+
{% if repo.matches %}
55+
<span class="dropdown" style="position:absolute; right:0px; top:0px;">
56+
<span>
57+
<a href="{% url 'projects_detail' repo.matches.0 %}">{% trans "View Project" %}</a>
58+
<a href="#">&#x25BC;</a>
59+
</span>
60+
<ul>
61+
{% for match in repo.matches %}
62+
<li><a href="{% url 'projects_detail' match %}">{{ match }}</a></li>
63+
{% endfor %}
64+
</ul>
65+
</span>
66+
67+
{% else %}
68+
<ul class="module-item-menu">
69+
<span>
70+
<input type="submit" value="Create" style="margin: 3px;" />
71+
</span>
72+
</ul>
73+
{% endif %}
74+
75+
</form>
76+
</li>
77+
{% endfor %}
78+
</ul>
79+
</div>
80+
</div>
81+
</div>
82+
</div>
83+
{% else %}
84+
{% url 'socialaccount_connections' as social_url %}
85+
{% blocktrans %}You don't have any BitBucket repositories connected.
86+
Go to your <a href="{{ social_url }}">Social Accounts</a> to set one up.
87+
{% endblocktrans %}
88+
{% endif %}
89+
{% endblock %}

0 commit comments

Comments
 (0)