SushantGautam commited on
Commit
bd98c1d
·
1 Parent(s): bd52915

Add social authentication and enhance database configuration

Browse files

- Integrated social_django for Google OAuth2 authentication.
- Updated database settings to include MySQL alongside SQLite.
- Implemented logout functionality and adjusted URL routing.
- Enhanced author admin display with additional fields.
- Added search hierarchy functionality in views.
- Created a new home page template with user authentication checks.
- Established API endpoint for search functionality.
- Implemented cursor pagination in OpenAlex data scraping.
- Added database routing logic for core app.

BridgeMentor/routers.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class AppRouter:
2
+ def db_for_read(self, model, **hints):
3
+ if model._meta.app_label == 'core':
4
+ return 'sqlite'
5
+ return 'default'
6
+
7
+ def db_for_write(self, model, **hints):
8
+ if model._meta.app_label == 'core':
9
+ return 'sqlite'
10
+ return 'default'
11
+
12
+ def allow_relation(self, obj1, obj2, **hints):
13
+ # Allow relations if both models are in the same DB
14
+ db_obj1 = self.db_for_read(obj1)
15
+ db_obj2 = self.db_for_read(obj2)
16
+ return db_obj1 == db_obj2
17
+
18
+ def allow_migrate(self, db, app_label, model_name=None, **hints):
19
+ if app_label == 'core':
20
+ return db == 'sqlite'
21
+ return db == 'default'
BridgeMentor/settings.py CHANGED
@@ -39,6 +39,7 @@ INSTALLED_APPS = [
39
  'django.contrib.staticfiles',
40
  'django_filters',
41
  'graphene_django',
 
42
  'core',
43
  ]
44
 
@@ -57,15 +58,19 @@ ROOT_URLCONF = 'BridgeMentor.urls'
57
  TEMPLATES = [
58
  {
59
  'BACKEND': 'django.template.backends.django.DjangoTemplates',
60
- 'DIRS': [],
61
  'APP_DIRS': True,
62
  'OPTIONS': {
63
  'context_processors': [
 
 
64
  'django.template.context_processors.debug',
65
  'django.template.context_processors.request',
66
  'django.contrib.auth.context_processors.auth',
67
  'django.contrib.messages.context_processors.messages',
 
68
  ],
 
69
  },
70
  },
71
  ]
@@ -77,12 +82,31 @@ WSGI_APPLICATION = 'BridgeMentor.wsgi.application'
77
  # https://docs.djangoproject.com/en/4.2/ref/settings/#databases
78
 
79
  DATABASES = {
80
- 'default': {
81
  'ENGINE': 'django.db.backends.sqlite3',
82
  'NAME': BASE_DIR / 'db.sqlite3',
83
- }
 
 
 
 
 
 
 
 
 
 
 
84
  }
85
 
 
 
 
 
 
 
 
 
86
 
87
  # Password validation
88
  # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
@@ -128,3 +152,10 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
128
  GRAPHENE = {
129
  "SCHEMA": "core.schema.schema" # Adjust to match your project
130
  }
 
 
 
 
 
 
 
 
39
  'django.contrib.staticfiles',
40
  'django_filters',
41
  'graphene_django',
42
+ 'social_django',
43
  'core',
44
  ]
45
 
 
58
  TEMPLATES = [
59
  {
60
  'BACKEND': 'django.template.backends.django.DjangoTemplates',
61
+ 'DIRS': ['core/templates'],
62
  'APP_DIRS': True,
63
  'OPTIONS': {
64
  'context_processors': [
65
+ 'social_django.context_processors.backends',
66
+ 'social_django.context_processors.login_redirect',
67
  'django.template.context_processors.debug',
68
  'django.template.context_processors.request',
69
  'django.contrib.auth.context_processors.auth',
70
  'django.contrib.messages.context_processors.messages',
71
+
72
  ],
73
+
74
  },
75
  },
76
  ]
 
82
  # https://docs.djangoproject.com/en/4.2/ref/settings/#databases
83
 
84
  DATABASES = {
85
+ 'sqlite': {
86
  'ENGINE': 'django.db.backends.sqlite3',
87
  'NAME': BASE_DIR / 'db.sqlite3',
88
+ },
89
+ 'default': {
90
+ 'ENGINE': 'django.db.backends.mysql',
91
+ 'NAME': 'cybersan_bridgementor',
92
+ 'USER': 'cybersan_bridgementor',
93
+ 'PASSWORD': 'bridgementor',
94
+ 'HOST': '192.250.235.27', # or your MariaDB host
95
+ 'PORT': '3306', # default MariaDB port
96
+ 'OPTIONS': {
97
+ 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
98
+ },
99
+ },
100
  }
101
 
102
+ DATABASE_ROUTERS = ['BridgeMentor.routers.AppRouter']
103
+
104
+ AUTHENTICATION_BACKENDS = (
105
+ 'social_core.backends.open_id.OpenIdAuth',
106
+ 'social_core.backends.google.GoogleOAuth2',
107
+ 'social_core.backends.twitter.TwitterOAuth',
108
+ 'django.contrib.auth.backends.ModelBackend',
109
+ )
110
 
111
  # Password validation
112
  # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
 
152
  GRAPHENE = {
153
  "SCHEMA": "core.schema.schema" # Adjust to match your project
154
  }
155
+
156
+ # SOCIAL_AUTH_REQUIRE_POST = True
157
+ SOCIAL_AUTH_URL_NAMESPACE = 'social'
158
+
159
+ SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '556457523-2d180pkqq6srm7rvg5veidjteqat3mvs.apps.googleusercontent.com'
160
+ SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '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'
161
+ LOGIN_REDIRECT_URL = '/'
BridgeMentor/urls.py CHANGED
@@ -16,10 +16,24 @@ Including another URLconf
16
  """
17
  from graphene_django.views import GraphQLView
18
  from django.contrib import admin
 
19
  from django.urls import path
 
 
 
 
 
 
 
 
20
 
21
  urlpatterns = [
22
  path('admin/', admin.site.urls),
23
  path("graphql/", GraphQLView.as_view(graphiql=True)),
 
 
 
 
 
24
 
25
  ]
 
16
  """
17
  from graphene_django.views import GraphQLView
18
  from django.contrib import admin
19
+ from django.contrib.auth import logout
20
  from django.urls import path
21
+ from django.urls import include
22
+ from django.shortcuts import redirect
23
+
24
+
25
+ def logout_view(request):
26
+ logout(request)
27
+ return redirect('/')
28
+
29
 
30
  urlpatterns = [
31
  path('admin/', admin.site.urls),
32
  path("graphql/", GraphQLView.as_view(graphiql=True)),
33
+ path("", include('social_django.urls', namespace="social")),
34
+ path('', include('core.urls')), # routes '' to your home app
35
+ # routes '' to your home app
36
+ path('logout', logout_view, name='logout'),
37
+
38
 
39
  ]
core/admin.py CHANGED
@@ -20,7 +20,7 @@ class InstitutionAdmin(admin.ModelAdmin):
20
  @admin.register(Author)
21
  class AuthorAdmin(admin.ModelAdmin):
22
  list_display = ('id', 'name', 'orcid', 'h_index',
23
- 'i10_index', 'cited_by_count', 'works_count')
24
  search_fields = ('name', 'orcid')
25
  list_filter = ('h_index',)
26
 
 
20
  @admin.register(Author)
21
  class AuthorAdmin(admin.ModelAdmin):
22
  list_display = ('id', 'name', 'orcid', 'h_index',
23
+ 'i10_index', 'cited_by_count', 'works_count', 'updated_at', 'created_at')
24
  search_fields = ('name', 'orcid')
25
  list_filter = ('h_index',)
26
 
core/templates/home.html ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- home/templates/home/home.html -->
2
+ <!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <title>My Home Page</title>
6
+ </head>
7
+
8
+ <body>
9
+ {% if user.is_authenticated %}
10
+ <span>Welcome, {{ user.first_name }}!</span> <a href="{% url 'logout' %}">Logout</a>
11
+ {% else %}
12
+ <a href="{% url 'social:begin' 'google-oauth2' %}">Login With Google</a>
13
+ {% endif %}
14
+
15
+ <h2>Search Domains</h2>
16
+ <input type="text" id="search-input" placeholder="Type something..." autocomplete="off" />
17
+ <ul id="results-list"></ul>
18
+
19
+ <script>
20
+ const input = document.getElementById('search-input')
21
+ const resultsList = document.getElementById('results-list')
22
+
23
+ input.addEventListener('input', async () => {
24
+ const query = input.value.trim()
25
+ if (!query) {
26
+ resultsList.innerHTML = ''
27
+ return
28
+ }
29
+
30
+ const res = await fetch(`/api/search/?q=${encodeURIComponent(query)}`)
31
+ const data = await res.json()
32
+
33
+ resultsList.innerHTML = ''
34
+ data.results.forEach((item) => {
35
+ const li = document.createElement('li')
36
+
37
+ const link = document.createElement('a')
38
+ link.href = `/${item.type.toLowerCase()}/${item.id}/`
39
+ link.textContent = item.text
40
+ link.style.textDecoration = 'none'
41
+ link.style.color = 'black'
42
+ li.appendChild(link)
43
+ resultsList.appendChild(li)
44
+ })
45
+ })
46
+ </script>
47
+ </body>
48
+ </html>
core/urls.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from django.urls import path
2
+ from .views import home_page, search_hierarchy
3
+
4
+ urlpatterns = [
5
+ path('', home_page, name='home'),
6
+ path("api/search/", search_hierarchy, name="search-hierarchy"),
7
+ ]
core/views.py CHANGED
@@ -1,3 +1,29 @@
 
 
1
  from django.shortcuts import render
2
 
3
- # Create your views here.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .models import Domain, Field, Subfield, Topic
2
+ from django.http import JsonResponse
3
  from django.shortcuts import render
4
 
5
+
6
+ def home_page(request):
7
+ return render(request, 'home.html')
8
+
9
+
10
+ def search_hierarchy(request):
11
+ q = request.GET.get("q", "").strip()
12
+ if not q:
13
+ return JsonResponse({"results": []})
14
+
15
+ results = []
16
+
17
+ results += [{"id": d.id, "text": d.name, "type": "Domain"}
18
+ for d in Domain.objects.filter(name__icontains=q)]
19
+
20
+ results += [{"id": f.id, "text": f"{f.domain.name} > {f.name}", "type": "Field"}
21
+ for f in Field.objects.select_related("domain").filter(name__icontains=q)]
22
+
23
+ results += [{"id": s.id, "text": f"{s.field.domain.name} > {s.field.name} > {s.name}", "type": "Subfield"}
24
+ for s in Subfield.objects.select_related("field__domain").filter(name__icontains=q)]
25
+
26
+ results += [{"id": t.id, "text": f"{t.subfield.field.domain.name} > {t.subfield.field.name} > {t.subfield.name} > {t.name}", "type": "Topic"}
27
+ for t in Topic.objects.select_related("subfield__field__domain").filter(name__icontains=q)]
28
+
29
+ return JsonResponse({"results": sorted(results, key=lambda x: x["text"].lower())[:10]})
populate_user.py CHANGED
@@ -20,7 +20,7 @@ def add_author(user_info, updated_date):
20
  author, _ = Author.objects.update_or_create(
21
  id=author_id,
22
  defaults={
23
- "name": max(user_info['display_name_alternatives'], key=lambda name: len(name)),
24
  "orcid": parse_id_from_url(user_info["orcid"]) if user_info.get("orcid") else None,
25
  "h_index": user_info["summary_stats"]["h_index"],
26
  "i10_index": user_info["summary_stats"]["i10_index"],
@@ -42,7 +42,7 @@ def add_institution(inst_data):
42
  "name": inst_data["display_name"],
43
  "ror_id": parse_id_from_url(inst_data["ror"]),
44
  "country_code": 'N/A' or inst_data.get("country_code"),
45
- "institution_type": inst_data["type"]
46
  }
47
  )
48
  return inst
 
20
  author, _ = Author.objects.update_or_create(
21
  id=author_id,
22
  defaults={
23
+ "name": max(user_info['display_name_alternatives'] + [user_info['display_name']], key=lambda name: len(name)),
24
  "orcid": parse_id_from_url(user_info["orcid"]) if user_info.get("orcid") else None,
25
  "h_index": user_info["summary_stats"]["h_index"],
26
  "i10_index": user_info["summary_stats"]["i10_index"],
 
42
  "name": inst_data["display_name"],
43
  "ror_id": parse_id_from_url(inst_data["ror"]),
44
  "country_code": 'N/A' or inst_data.get("country_code"),
45
+ "institution_type": 'N/A' or inst_data.get("institution_type")
46
  }
47
  )
48
  return inst
requirements.txt CHANGED
@@ -1,3 +1,4 @@
1
  django==4.2.20
2
  graphene-django
3
- django-filter
 
 
1
  django==4.2.20
2
  graphene-django
3
+ django-filter
4
+ mysqlclient?
scrap_openalex.py CHANGED
@@ -7,32 +7,41 @@ from time import sleep
7
  BASE_URL = "https://api.openalex.org/authors"
8
  FILTER = "last_known_institutions.country_code:NO,x_concepts.id:C41008148"
9
  PER_PAGE = 200
10
- TOTAL_RESULTS = 86500
11
- TOTAL_PAGES = (TOTAL_RESULTS + PER_PAGE - 1) // PER_PAGE # Ceiling division
12
 
13
- # Output directory
14
  OUTPUT_DIR = "C41008148_authors"
15
  os.makedirs(OUTPUT_DIR, exist_ok=True)
16
 
17
- # Loop through pages
18
- for page in range(50, TOTAL_PAGES + 1):
19
- url = f"{BASE_URL}?filter={FILTER}&per-page={PER_PAGE}&page={page}"
 
 
 
20
  try:
21
- print(f"Fetching page {page}...")
22
  response = requests.get(url)
23
  response.raise_for_status()
24
  data = response.json()
25
 
26
- filename = os.path.join(OUTPUT_DIR, f"{page:010}.json")
27
- # skip if exists
28
  if os.path.exists(filename):
29
  print(f"File {filename} already exists, skipping...")
 
 
30
  continue
 
31
  with open(filename, 'w', encoding='utf-8') as f:
32
  json.dump(data, f, ensure_ascii=False, indent=2)
33
- sleep(1) # Rate-limiting to avoid hitting the server too hard
 
 
 
 
 
 
 
34
  except Exception as e:
35
- print(f"Error on page {page}: {e}")
36
  break
37
 
38
- print("Download complete.")
 
7
  BASE_URL = "https://api.openalex.org/authors"
8
  FILTER = "last_known_institutions.country_code:NO,x_concepts.id:C41008148"
9
  PER_PAGE = 200
 
 
10
 
 
11
  OUTPUT_DIR = "C41008148_authors"
12
  os.makedirs(OUTPUT_DIR, exist_ok=True)
13
 
14
+ # Initialize cursor
15
+ cursor = "*"
16
+ page_count = 1 # Track page numbers for saving files
17
+
18
+ while cursor:
19
+ url = f"{BASE_URL}?filter={FILTER}&per-page={PER_PAGE}&cursor={cursor}"
20
  try:
21
+ print(f"Fetching page {page_count} with cursor...")
22
  response = requests.get(url)
23
  response.raise_for_status()
24
  data = response.json()
25
 
26
+ filename = os.path.join(OUTPUT_DIR, f"{page_count:010}.json")
 
27
  if os.path.exists(filename):
28
  print(f"File {filename} already exists, skipping...")
29
+ cursor = data.get("meta", {}).get("next_cursor")
30
+ page_count += 1
31
  continue
32
+
33
  with open(filename, 'w', encoding='utf-8') as f:
34
  json.dump(data, f, ensure_ascii=False, indent=2)
35
+
36
+ cursor = data.get("meta", {}).get("next_cursor")
37
+ if not cursor:
38
+ print("No more results.")
39
+ break
40
+
41
+ page_count += 1
42
+ sleep(1) # Rate-limiting
43
  except Exception as e:
44
+ print(f"Error on page {page_count}: {e}")
45
  break
46
 
47
+ print("Download complete using cursor pagination.")
sql_test.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pymysql
2
+
3
+ try:
4
+ print("Connecting to MariaDB...")
5
+ conn = pymysql.connect(
6
+ host="192.250.235.27", # or your DB host, e.g. "127.0.0.1"
7
+ port=3306,
8
+ user="cybersan_bridgementor", # replace with your MySQL username
9
+ password="bridgementor", # replace with your MySQL password
10
+ database="cybersan_bridgementor", # replace with your database name
11
+ )
12
+ print("Connected successfully.")
13
+
14
+ cursor = conn.cursor()
15
+ print("Fetching table list...")
16
+ cursor.execute("SHOW TABLES")
17
+
18
+ print("Tables in the database:")
19
+ for (table_name,) in cursor.fetchall():
20
+ print(f"- {table_name}")
21
+
22
+ cursor.close()
23
+ conn.close()
24
+ print("Connection closed.")
25
+
26
+ except pymysql.MySQLError as e:
27
+ print(f"MariaDB connection error: {e}")